You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pivot.apache.org by tv...@apache.org on 2009/03/26 00:12:17 UTC

svn commit: r758461 [33/47] - in /incubator/pivot/branches: ./ 1.1/ 1.1/charts-test/ 1.1/charts-test/src/ 1.1/charts-test/src/pivot/ 1.1/charts-test/src/pivot/charts/ 1.1/charts-test/src/pivot/charts/test/ 1.1/charts/ 1.1/charts/lib/ 1.1/charts/src/ 1....

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/ScrollPaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/ScrollPaneSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/ScrollPaneSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/ScrollPaneSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,973 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.wtk.skin;
+
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Keyboard;
+import pivot.wtk.Mouse;
+import pivot.wtk.Orientation;
+import pivot.wtk.Bounds;
+import pivot.wtk.ScrollBar;
+import pivot.wtk.ScrollBarValueListener;
+import pivot.wtk.ScrollPane;
+import pivot.wtk.ScrollPane.Corner;
+import pivot.wtk.ScrollPane.ScrollBarPolicy;
+import pivot.wtk.ScrollPaneListener;
+import pivot.wtk.Viewport;
+import pivot.wtk.ViewportListener;
+
+/**
+ * Scroll pane skin.
+ *
+ * @author tvolkert
+ */
+public class ScrollPaneSkin extends ContainerSkin
+    implements Viewport.Skin, ScrollPaneListener, ViewportListener,
+               ScrollBarValueListener {
+
+    private ScrollBar horizontalScrollBar = new ScrollBar(Orientation.HORIZONTAL);
+    private ScrollBar verticalScrollBar = new ScrollBar(Orientation.VERTICAL);
+
+    private Corner topLeftCorner = new Corner(Corner.Placement.TOP_LEFT);
+    private Corner bottomLeftCorner = new Corner(Corner.Placement.BOTTOM_LEFT);
+    private Corner bottomRightCorner = new Corner(Corner.Placement.BOTTOM_RIGHT);
+    private Corner topRightCorner = new Corner(Corner.Placement.TOP_RIGHT);
+
+    private int horizontalReveal = 30;
+    private int verticalReveal = 30;
+
+    private int cachedHorizontalScrollBarHeight = 0;
+    private int cachedVerticalScrollBarWidth = 0;
+
+    private static final int DEFAULT_HORIZONTAL_INCREMENT = 10;
+    private static final int DEFAULT_VERTICAL_INCREMENT = 10;
+
+    public ScrollPaneSkin() {
+        super();
+
+        horizontalScrollBar.setUnitIncrement(DEFAULT_HORIZONTAL_INCREMENT);
+        verticalScrollBar.setUnitIncrement(DEFAULT_VERTICAL_INCREMENT);
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        ScrollPane scrollPane = (ScrollPane)component;
+        scrollPane.getViewportListeners().add(this);
+        scrollPane.getScrollPaneListeners().add(this);
+
+        scrollPane.add(horizontalScrollBar);
+        scrollPane.add(verticalScrollBar);
+
+        scrollPane.add(topLeftCorner);
+        scrollPane.add(bottomLeftCorner);
+        scrollPane.add(bottomRightCorner);
+        scrollPane.add(topRightCorner);
+
+        horizontalScrollBar.getScrollBarValueListeners().add(this);
+        verticalScrollBar.getScrollBarValueListeners().add(this);
+    }
+
+    @Override
+    public void uninstall() {
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        scrollPane.getViewportListeners().remove(this);
+        scrollPane.getScrollPaneListeners().remove(this);
+
+        scrollPane.remove(horizontalScrollBar);
+        scrollPane.remove(verticalScrollBar);
+
+        scrollPane.remove(topLeftCorner);
+        scrollPane.remove(bottomLeftCorner);
+        scrollPane.remove(bottomRightCorner);
+        scrollPane.remove(topRightCorner);
+
+        horizontalScrollBar.getScrollBarValueListeners().remove(this);
+        verticalScrollBar.getScrollBarValueListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    @Override
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        Component view = scrollPane.getView();
+
+        if (view != null) {
+            int preferredRowHeaderWidth = 0;
+            Component rowHeader = scrollPane.getRowHeader();
+            if (rowHeader != null) {
+                preferredRowHeaderWidth = rowHeader.getPreferredWidth(-1);
+            }
+
+            int preferredColumnHeaderHeight = 0;
+            Component columnHeader = scrollPane.getColumnHeader();
+            if (columnHeader != null) {
+                preferredColumnHeaderHeight = columnHeader.getPreferredHeight(-1);
+            }
+
+            ScrollBarPolicy verticalPolicy = scrollPane.getVerticalScrollBarPolicy();
+
+            if (verticalPolicy != ScrollBarPolicy.FILL) {
+                // Get the unconstrained preferred size of the view
+                Dimensions preferredViewSize = view.getPreferredSize();
+
+                // If the policy is FILL_TO_CAPACITY, and the sum of the
+                // unconstrained preferred heights of the view and the column
+                // header is less than the height constraint, apply the FILL
+                // policy; otherwise, apply the AUTO policy
+
+                if (verticalPolicy == ScrollBarPolicy.FILL_TO_CAPACITY) {
+                    if (height < 0) {
+                        verticalPolicy = ScrollBarPolicy.AUTO;
+                    } else {
+                        int preferredHeight = preferredViewSize.height +
+                            preferredColumnHeaderHeight;
+
+                        if (preferredHeight < height) {
+                            verticalPolicy = ScrollBarPolicy.FILL;
+                        } else {
+                            verticalPolicy = ScrollBarPolicy.AUTO;
+                        }
+                    }
+                }
+
+                // If the policy is ALWAYS, NEVER, or AUTO, the preferred
+                // width is the sum of the unconstrained preferred widths of
+                // the view and row header, plus the width of the scroll
+                // bar if policy is ALWAYS or if the view's preferred height is
+                // greater than the height constraint and the policy is AUTO
+
+                if (verticalPolicy == ScrollBarPolicy.ALWAYS
+                    || verticalPolicy == ScrollBarPolicy.NEVER
+                    || verticalPolicy == ScrollBarPolicy.AUTO) {
+                    preferredWidth = preferredViewSize.width +
+                        preferredRowHeaderWidth;
+
+                    // If the sum of the preferred heights of the view and the
+                    // column header is greater than the height constraint,
+                    // include the preferred width of the scroll bar in the
+                    // preferred width calculation
+                    if (verticalPolicy == ScrollBarPolicy.ALWAYS
+                        || (verticalPolicy == ScrollBarPolicy.AUTO
+                        && height > 0
+                        && preferredViewSize.height + preferredColumnHeaderHeight > height)) {
+                        preferredWidth += verticalScrollBar.getPreferredWidth(-1);
+                    }
+                }
+            }
+
+            if (verticalPolicy == ScrollBarPolicy.FILL) {
+                // Preferred width is the sum of the constrained preferred
+                // width of the view and the unconstrained preferred width of
+                // the row header
+
+                if (height >= 0) {
+                    // Subtract the unconstrained preferred height of the
+                    // column header from the height constraint
+                    height = Math.max(height - preferredColumnHeaderHeight, 0);
+                }
+
+                preferredWidth = view.getPreferredWidth(height) +
+                    preferredRowHeaderWidth;
+            }
+        }
+
+        return preferredWidth;
+    }
+
+    @Override
+    public int getPreferredHeight(int width) {
+        int preferredHeight = 0;
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        Component view = scrollPane.getView();
+
+        if (view != null) {
+            int preferredRowHeaderWidth = 0;
+            Component rowHeader = scrollPane.getRowHeader();
+            if (rowHeader != null) {
+                preferredRowHeaderWidth = rowHeader.getPreferredWidth(-1);
+            }
+
+            int preferredColumnHeaderHeight = 0;
+            Component columnHeader = scrollPane.getColumnHeader();
+            if (columnHeader != null) {
+                preferredColumnHeaderHeight = columnHeader.getPreferredHeight(-1);
+            }
+
+            ScrollBarPolicy horizontalPolicy = scrollPane.getHorizontalScrollBarPolicy();
+
+            if (horizontalPolicy != ScrollBarPolicy.FILL) {
+                // Get the unconstrained preferred size of the view
+                Dimensions preferredViewSize = view.getPreferredSize();
+
+                // If the policy is FILL_TO_CAPACITY, and the sum of the
+                // unconstrained preferred widths of the view and the row
+                // header is less than the width constraint, apply the FILL
+                // policy; otherwise, apply the AUTO policy
+
+                if (horizontalPolicy == ScrollBarPolicy.FILL_TO_CAPACITY) {
+                    if (width < 0) {
+                        horizontalPolicy = ScrollBarPolicy.AUTO;
+                    } else {
+                        int preferredWidth = preferredViewSize.width +
+                            preferredRowHeaderWidth;
+
+                        if (preferredWidth < width) {
+                            horizontalPolicy = ScrollBarPolicy.FILL;
+                        } else {
+                            horizontalPolicy = ScrollBarPolicy.AUTO;
+                        }
+                    }
+                }
+
+                // If the policy is ALWAYS, NEVER, or AUTO, the preferred
+                // height is the sum of the unconstrained preferred heights of
+                // the view and column header, plus the height of the scroll
+                // bar if policy is ALWAYS or if the view's preferred width is
+                // greater than the width constraint and the policy is AUTO
+
+                if (horizontalPolicy == ScrollBarPolicy.ALWAYS
+                    || horizontalPolicy == ScrollBarPolicy.NEVER
+                    || horizontalPolicy == ScrollBarPolicy.AUTO) {
+                    preferredHeight = preferredViewSize.height +
+                        preferredColumnHeaderHeight;
+
+                    // If the sum of the preferred widths of the view and the
+                    // row header is greater than the width constraint, include
+                    // the preferred height of the scroll bar in the preferred
+                    // height calculation
+                    if (horizontalPolicy == ScrollBarPolicy.ALWAYS
+                        || (horizontalPolicy == ScrollBarPolicy.AUTO
+                        && width > 0
+                        && preferredViewSize.width + preferredRowHeaderWidth > width)) {
+                        preferredHeight += horizontalScrollBar.getPreferredHeight(-1);
+                    }
+                }
+            }
+
+            if (horizontalPolicy == ScrollBarPolicy.FILL) {
+                // Preferred height is the sum of the constrained preferred height
+                // of the view and the unconstrained preferred height of the column
+                // header
+
+                if (width >= 0) {
+                    // Subtract the unconstrained preferred width of the row header
+                    // from the width constraint
+                    width = Math.max(width - preferredRowHeaderWidth, 0);
+                }
+
+                preferredHeight = view.getPreferredHeight(width) +
+                    preferredColumnHeaderHeight;
+            }
+        }
+
+        return preferredHeight;
+    }
+
+    @Override
+    public Dimensions getPreferredSize() {
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+
+        int preferredWidth = 0;
+        int preferredHeight = 0;
+
+        Component view = scrollPane.getView();
+        if (view != null) {
+            Dimensions preferredViewSize = view.getPreferredSize();
+
+            preferredWidth += preferredViewSize.width;
+            preferredHeight += preferredViewSize.height;
+
+            Component rowHeader = scrollPane.getRowHeader();
+            if (rowHeader != null) {
+                preferredWidth += rowHeader.getPreferredWidth(-1);
+            }
+
+            Component columnHeader = scrollPane.getColumnHeader();
+            if (columnHeader != null) {
+                preferredHeight += columnHeader.getPreferredHeight(-1);
+            }
+
+            if (scrollPane.getHorizontalScrollBarPolicy() == ScrollBarPolicy.ALWAYS) {
+                preferredHeight += horizontalScrollBar.getPreferredHeight(-1);
+            }
+
+            if (scrollPane.getVerticalScrollBarPolicy() == ScrollBarPolicy.ALWAYS) {
+                preferredWidth += verticalScrollBar.getPreferredWidth(-1);
+            }
+        }
+
+        return new Dimensions(preferredWidth, preferredHeight);
+    }
+
+    @Override
+    public boolean mouseWheel(Component component, Mouse.ScrollType scrollType, int scrollAmount,
+        int wheelRotation, int x, int y) {
+        boolean consumed = false;
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        Component view = scrollPane.getView();
+
+        if (view != null) {
+            // The scroll orientation is tied to whether the shift key was
+            // presssed while the mouse wheel was scrolled
+            if (Keyboard.isPressed(Keyboard.Modifier.SHIFT)) {
+                // Treat the mouse wheel as a horizontal scroll event
+                int previousScrollLeft = scrollPane.getScrollLeft();
+                int newScrollLeft = previousScrollLeft + (scrollAmount * wheelRotation *
+                    horizontalScrollBar.getUnitIncrement());
+
+                if (wheelRotation > 0) {
+                    int maxScrollLeft = getMaxScrollLeft();
+                    newScrollLeft = Math.min(newScrollLeft, maxScrollLeft);
+
+                    if (previousScrollLeft < maxScrollLeft) {
+                        consumed = true;
+                    }
+                } else {
+                    newScrollLeft = Math.max(newScrollLeft, 0);
+
+                    if (previousScrollLeft > 0) {
+                        consumed = true;
+                    }
+                }
+
+                scrollPane.setScrollLeft(newScrollLeft);
+            } else {
+                // Treat the mouse wheel as a vertical scroll event
+                int previousScrollTop = scrollPane.getScrollTop();
+                int newScrollTop = previousScrollTop + (scrollAmount * wheelRotation *
+                    verticalScrollBar.getUnitIncrement());
+
+                if (wheelRotation > 0) {
+                    int maxScrollTop = getMaxScrollTop();
+                    newScrollTop = Math.min(newScrollTop, maxScrollTop);
+
+                    if (previousScrollTop < maxScrollTop) {
+                        consumed = true;
+                    }
+                } else {
+                    newScrollTop = Math.max(newScrollTop, 0);
+
+                    if (previousScrollTop > 0) {
+                        consumed = true;
+                    }
+                }
+
+                scrollPane.setScrollTop(newScrollTop);
+            }
+        }
+
+        return consumed;
+    }
+
+    @Override
+    public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
+        boolean consumed = super.keyPressed(component, keyCode, keyLocation);
+
+        if (!consumed) {
+            ScrollPane scrollPane = (ScrollPane)getComponent();
+
+            int scrollTop = scrollPane.getScrollTop();
+            int scrollLeft = scrollPane.getScrollLeft();
+
+            if (keyCode == Keyboard.KeyCode.UP) {
+                int newScrollTop = Math.max(scrollTop -
+                    verticalScrollBar.getUnitIncrement(), 0);
+
+                scrollPane.setScrollTop(newScrollTop);
+
+                consumed = (newScrollTop != scrollTop);
+            } else if (keyCode == Keyboard.KeyCode.DOWN) {
+                int newScrollTop = Math.min(scrollTop +
+                    verticalScrollBar.getUnitIncrement(), getMaxScrollTop());
+
+                scrollPane.setScrollTop(newScrollTop);
+
+                consumed = (newScrollTop != scrollTop);
+            } else if (keyCode == Keyboard.KeyCode.LEFT) {
+                int newScrollLeft = Math.max(scrollLeft -
+                    horizontalScrollBar.getUnitIncrement(), 0);
+
+                scrollPane.setScrollLeft(newScrollLeft);
+
+                consumed = (newScrollLeft != scrollLeft);
+            } else if (keyCode == Keyboard.KeyCode.RIGHT) {
+                int newScrollLeft = Math.min(scrollLeft +
+                    horizontalScrollBar.getUnitIncrement(), getMaxScrollLeft());
+
+                scrollPane.setScrollLeft(newScrollLeft);
+
+                consumed = (newScrollLeft != scrollLeft);
+            } else if (keyCode == Keyboard.KeyCode.PAGE_UP) {
+                int increment = verticalScrollBar.getBlockIncrement();
+                int newScrollTop = Math.max(scrollTop - increment, 0);
+
+                scrollPane.setScrollTop(newScrollTop);
+
+                consumed = (newScrollTop != scrollTop);
+            } else if (keyCode == Keyboard.KeyCode.PAGE_DOWN) {
+                int increment = verticalScrollBar.getBlockIncrement();
+                int newScrollTop = Math.min(scrollTop + increment, getMaxScrollTop());
+
+                scrollPane.setScrollTop(newScrollTop);
+
+                consumed = (newScrollTop != scrollTop);
+            }
+        }
+
+        return consumed;
+    }
+
+    /**
+     * Gets the maximum legal <tt>scrollTop</tt> value this this skin imposes.
+     * This is the largest value possible that still shows as much of the view
+     * component as it can.
+     *
+     * @return
+     * The maximum scrollTop value
+     */
+    private int getMaxScrollTop() {
+        int maxScrollTop = 0;
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        Component view = scrollPane.getView();
+
+        if (view != null) {
+            int viewHeight = view.getHeight();
+            int columnHeaderHeight = 0;
+            int horizontalScrollBarHeight = 0;
+            int height = getHeight();
+
+            Component columnHeader = scrollPane.getColumnHeader();
+            if (columnHeader != null) {
+                columnHeaderHeight = columnHeader.getHeight();
+            }
+
+            if (horizontalScrollBar.isVisible()) {
+                horizontalScrollBarHeight = horizontalScrollBar.getHeight();
+            }
+
+            maxScrollTop = Math.max(viewHeight + columnHeaderHeight +
+                horizontalScrollBarHeight - height, 0);
+        }
+
+        return maxScrollTop;
+    }
+
+    /**
+     * Gets the maximum legal <tt>scrollLeft</tt> value this this skin imposes.
+     * This is the largest value possible that still shows as much of the view
+     * component as it can.
+     *
+     * @return
+     * The maximum scrollLeft value
+     */
+    private int getMaxScrollLeft() {
+        int maxScrollLeft = 0;
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+        Component view = scrollPane.getView();
+
+        if (view != null) {
+            int viewWidth = view.getWidth();
+            int rowHeaderWidth = 0;
+            int verticalScrollBarWidth = 0;
+            int width = getWidth();
+
+            Component rowHeader = scrollPane.getRowHeader();
+            if (rowHeader != null) {
+                rowHeaderWidth = rowHeader.getWidth();
+            }
+
+            if (verticalScrollBar.isVisible()) {
+                verticalScrollBarWidth = verticalScrollBar.getWidth();
+            }
+
+            maxScrollLeft = Math.max(viewWidth + rowHeaderWidth +
+                verticalScrollBarWidth - width, 0);
+        }
+
+        return maxScrollLeft;
+    }
+
+    public void layout() {
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+
+        ScrollBarPolicy horizontalPolicy = scrollPane.getHorizontalScrollBarPolicy();
+        ScrollBarPolicy verticalPolicy = scrollPane.getVerticalScrollBarPolicy();
+
+        boolean fillWidthToCapacity = false;
+        boolean fillHeightToCapacity = false;
+
+        // The FILL_TO_CAPACITY policy means that we try to use AUTO, and only
+        // if it ends up not being wide or tall enough do we use FILL
+
+        if (horizontalPolicy == ScrollBarPolicy.FILL_TO_CAPACITY) {
+            horizontalPolicy = ScrollBarPolicy.AUTO;
+            fillWidthToCapacity = true;
+        }
+
+        if (verticalPolicy == ScrollBarPolicy.FILL_TO_CAPACITY) {
+            verticalPolicy = ScrollBarPolicy.AUTO;
+            fillHeightToCapacity = true;
+        }
+
+        layoutHelper(horizontalPolicy, verticalPolicy);
+
+        Component view = scrollPane.getView();
+        if (view != null && (fillWidthToCapacity || fillHeightToCapacity)) {
+            // We assumed AUTO. Now we check our assumption to see if we
+            // need to adjust it to use FILL
+            boolean adjustWidth = false, adjustHeight = false;
+
+            if (fillWidthToCapacity) {
+                Component rowHeader = scrollPane.getRowHeader();
+                int rowHeaderWidth = rowHeader != null ? rowHeader.getWidth() : 0;
+
+                int verticalScrollBarWidth = verticalScrollBar.isVisible() ?
+                    verticalScrollBar.getWidth() : 0;
+                int minViewWidth = getWidth() - rowHeaderWidth - verticalScrollBarWidth;
+
+                if (view.getWidth() < minViewWidth) {
+                    horizontalPolicy = ScrollBarPolicy.FILL;
+                    adjustWidth = true;
+                }
+            }
+
+            if (fillHeightToCapacity) {
+                Component columnHeader = scrollPane.getColumnHeader();
+                int columnHeaderHeight = columnHeader != null ?
+                    columnHeader.getHeight() : 0;
+
+                int horizontalScrollBarHeight = horizontalScrollBar.isVisible() ?
+                    horizontalScrollBar.getHeight() : 0;
+                int minViewHeight = getHeight() - columnHeaderHeight -
+                    horizontalScrollBarHeight;
+
+                if (view.getHeight() < minViewHeight) {
+                    verticalPolicy = ScrollBarPolicy.FILL;
+                    adjustHeight = true;
+                }
+            }
+
+            if (adjustWidth || adjustHeight) {
+                layoutHelper(horizontalPolicy, verticalPolicy);
+            }
+        }
+
+        cachedHorizontalScrollBarHeight = horizontalScrollBar.getHeight();
+        cachedVerticalScrollBarWidth = verticalScrollBar.getWidth();
+    }
+
+    /**
+     * Layout helper method that assumes that the <tt>FILL_TO_CAPACITY</tt>
+     * scroll policy doesn't exist.
+     *
+     * @param horizontalPolicy
+     * The assumed horizontal scroll policy; musn't be <tt>FILL_TO_CAPACITY</tt>
+     *
+     * @param vertical policy
+     * The assumed vertical scroll policy; musn't be <tt>FILL_TO_CAPACITY</tt>
+     */
+    private void layoutHelper(ScrollBarPolicy horizontalPolicy,
+        ScrollBarPolicy verticalPolicy) {
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+
+        int width = getWidth();
+        int height = getHeight();
+
+        boolean constrainWidth = (horizontalPolicy == ScrollBarPolicy.FILL);
+        boolean constrainHeight = (verticalPolicy == ScrollBarPolicy.FILL);
+
+        Component view = scrollPane.getView();
+        Component columnHeader = scrollPane.getColumnHeader();
+        Component rowHeader = scrollPane.getRowHeader();
+        Component corner = scrollPane.getCorner();
+
+        int rowHeaderWidth = 0;
+        if (rowHeader != null) {
+            rowHeaderWidth = rowHeader.getPreferredWidth(-1);
+        }
+
+        int columnHeaderHeight = 0;
+        if (columnHeader != null) {
+            columnHeaderHeight = columnHeader.getPreferredHeight(-1);
+        }
+
+        int previousViewWidth, viewWidth = 0;
+        int previousViewHeight, viewHeight = 0;
+        int previousHorizontalScrollBarHeight, horizontalScrollBarHeight = cachedHorizontalScrollBarHeight;
+        int previousVerticalScrollBarWidth, verticalScrollBarWidth = cachedVerticalScrollBarWidth;
+        int i = 0;
+
+        do {
+            previousViewWidth = viewWidth;
+            previousViewHeight = viewHeight;
+            previousHorizontalScrollBarHeight = horizontalScrollBarHeight;
+            previousVerticalScrollBarWidth = verticalScrollBarWidth;
+
+            if (view != null) {
+                if (constrainWidth && constrainHeight) {
+                    viewWidth = Math.max
+                        (width - rowHeaderWidth - verticalScrollBarWidth, 0);
+                    viewHeight = Math.max
+                        (height - columnHeaderHeight - horizontalScrollBarHeight, 0);
+                } else if (constrainWidth) {
+                    viewWidth = Math.max
+                        (width - rowHeaderWidth - verticalScrollBarWidth, 0);
+                    viewHeight = view.getPreferredHeight(viewWidth);
+                } else if (constrainHeight) {
+                    viewHeight = Math.max
+                        (height - columnHeaderHeight - horizontalScrollBarHeight, 0);
+                    viewWidth = view.getPreferredWidth(viewHeight);
+                } else {
+                    Dimensions viewPreferredSize = view.getPreferredSize();
+                    viewWidth = viewPreferredSize.width;
+                    viewHeight = viewPreferredSize.height;
+                }
+            }
+
+            if (horizontalPolicy == ScrollBarPolicy.ALWAYS
+                || (horizontalPolicy == ScrollBarPolicy.AUTO
+                && viewWidth > width - rowHeaderWidth - verticalScrollBarWidth)) {
+                horizontalScrollBarHeight = horizontalScrollBar.getPreferredHeight(-1);
+            } else {
+                horizontalScrollBarHeight = 0;
+            }
+
+            if (verticalPolicy == ScrollBarPolicy.ALWAYS
+                || (verticalPolicy == ScrollBarPolicy.AUTO
+                && viewHeight > height - columnHeaderHeight - horizontalScrollBarHeight)) {
+                verticalScrollBarWidth = verticalScrollBar.getPreferredWidth(-1);
+            } else {
+                verticalScrollBarWidth = 0;
+            }
+
+            if (++i > 4) {
+                // Infinite loop protection
+                System.err.println("Breaking out of potential infinite loop");
+                break;
+            }
+        } while (viewWidth != previousViewWidth
+            || viewHeight != previousViewHeight
+            || horizontalScrollBarHeight != previousHorizontalScrollBarHeight
+            || verticalScrollBarWidth != previousVerticalScrollBarWidth);
+
+        int scrollTop = scrollPane.getScrollTop();
+        int scrollLeft = scrollPane.getScrollLeft();
+
+        if (view != null) {
+            view.setSize(viewWidth, viewHeight);
+            view.setLocation(rowHeaderWidth - scrollLeft, columnHeaderHeight - scrollTop);
+        }
+
+        if (columnHeader != null) {
+            columnHeader.setSize(viewWidth, columnHeaderHeight);
+            columnHeader.setLocation(rowHeaderWidth - scrollLeft, 0);
+        }
+
+        if (rowHeader != null) {
+            rowHeader.setSize(rowHeaderWidth, viewHeight);
+            rowHeader.setLocation(0, columnHeaderHeight - scrollTop);
+        }
+
+        if (horizontalScrollBarHeight > 0) {
+            horizontalScrollBar.setVisible(true);
+
+            int horizontalScrollBarWidth = Math.max
+               (width - rowHeaderWidth - verticalScrollBarWidth, 0);
+            horizontalScrollBar.setSize(horizontalScrollBarWidth,
+                horizontalScrollBarHeight);
+            horizontalScrollBar.setLocation(rowHeaderWidth,
+                height - horizontalScrollBarHeight);
+        } else {
+            horizontalScrollBar.setVisible(false);
+        }
+
+        if (verticalScrollBarWidth > 0) {
+            verticalScrollBar.setVisible(true);
+
+            int verticalScrollBarHeight = Math.max
+               (height - columnHeaderHeight - horizontalScrollBarHeight, 0);
+            verticalScrollBar.setSize(verticalScrollBarWidth,
+                verticalScrollBarHeight);
+            verticalScrollBar.setLocation(width - verticalScrollBarWidth,
+                columnHeaderHeight);
+        } else {
+            verticalScrollBar.setVisible(false);
+        }
+
+        // Handle corner components
+
+        if (columnHeaderHeight > 0
+            && rowHeaderWidth > 0) {
+            if (corner != null) {
+                corner.setVisible(true);
+                corner.setSize(rowHeaderWidth, columnHeaderHeight);
+                corner.setLocation(0, 0);
+
+                topLeftCorner.setVisible(false);
+            } else {
+                topLeftCorner.setVisible(true);
+                topLeftCorner.setSize(rowHeaderWidth, columnHeaderHeight);
+                topLeftCorner.setLocation(0, 0);
+            }
+        } else {
+            if (corner != null) {
+                corner.setVisible(false);
+            }
+
+            topLeftCorner.setVisible(false);
+        }
+
+        if (rowHeaderWidth > 0
+            && horizontalScrollBarHeight > 0) {
+            bottomLeftCorner.setVisible(true);
+            bottomLeftCorner.setSize(rowHeaderWidth, horizontalScrollBarHeight);
+            bottomLeftCorner.setLocation(0, height - horizontalScrollBarHeight);
+        } else {
+            bottomLeftCorner.setVisible(false);
+        }
+
+        if (verticalScrollBarWidth > 0
+            && horizontalScrollBarHeight > 0) {
+            bottomRightCorner.setVisible(true);
+            bottomRightCorner.setSize(verticalScrollBarWidth, horizontalScrollBarHeight);
+            bottomRightCorner.setLocation(width - verticalScrollBarWidth,
+                height - horizontalScrollBarHeight);
+        } else {
+            bottomRightCorner.setVisible(false);
+        }
+
+        if (columnHeaderHeight > 0
+            && verticalScrollBarWidth > 0) {
+            topRightCorner.setVisible(true);
+            topRightCorner.setSize(verticalScrollBarWidth, columnHeaderHeight);
+            topRightCorner.setLocation(width - verticalScrollBarWidth, 0);
+        } else {
+            topRightCorner.setVisible(false);
+        }
+
+        // Perform bounds checking on the scrollTop and scrollLeft values,
+        // and adjust them as necessary. Make sure to do this after we've laid
+        // everything out, since our ViewPortListener methods rely on valid
+        // sizes from our components.
+
+        int maxScrollTop = getMaxScrollTop();
+        if (scrollTop > maxScrollTop) {
+            scrollPane.setScrollTop(maxScrollTop);
+        }
+
+        int maxScrollLeft = getMaxScrollLeft();
+        if (scrollLeft > maxScrollLeft) {
+            scrollPane.setScrollLeft(maxScrollLeft);
+        }
+
+        // Adjust the structure of our scroll bars. Make sure to do this after
+        // we adjust the scrollTop and scrollLeft values; otherwise we might
+        // try to set structure values that are out of bounds.
+
+        int viewportWidth = Math.max(width - rowHeaderWidth - verticalScrollBarWidth, 0);
+        horizontalScrollBar.setScope(0, viewWidth, Math.min(viewWidth, viewportWidth));
+        horizontalScrollBar.setBlockIncrement(Math.max(1, viewportWidth - horizontalReveal));
+
+        int viewportHeight = Math.max(height - columnHeaderHeight - horizontalScrollBarHeight, 0);
+        verticalScrollBar.setScope(0, viewHeight, Math.min(viewHeight, viewportHeight));
+        verticalScrollBar.setBlockIncrement(Math.max(1, viewportHeight - verticalReveal));
+    }
+
+    public int getHorizontalIncrement() {
+        return horizontalScrollBar.getUnitIncrement();
+    }
+
+    public void setHorizontalIncrement(int horizontalIncrement) {
+        horizontalScrollBar.setUnitIncrement(horizontalIncrement);
+    }
+
+    public int getVerticalIncrement() {
+        return verticalScrollBar.getUnitIncrement();
+    }
+
+    public void setVerticalIncrement(int verticalIncrement) {
+        verticalScrollBar.setUnitIncrement(verticalIncrement);
+    }
+
+    public int getHorizontalReveal() {
+        return horizontalReveal;
+    }
+
+    public void setHorizontalReveal(int horizontalReveal) {
+        this.horizontalReveal = horizontalReveal;
+    }
+
+    public int getVerticalReveal() {
+        return verticalReveal;
+    }
+
+    public void setVerticalReveal(int verticalReveal) {
+        this.verticalReveal = verticalReveal;
+    }
+
+    // Viewport.Skin methods
+
+    public Bounds getViewportBounds() {
+        int x = 0;
+        int y = 0;
+        int width = getWidth();
+        int height = getHeight();
+
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+
+        Component rowHeader = scrollPane.getRowHeader();
+        if (rowHeader != null) {
+            int rowHeaderWidth = rowHeader.getWidth();
+
+            x += rowHeaderWidth;
+            width -= rowHeaderWidth;
+        }
+
+        Component columnHeader = scrollPane.getColumnHeader();
+        if (columnHeader != null) {
+            int columnHeaderHeight = columnHeader.getHeight();
+
+            y += columnHeaderHeight;
+            height -= columnHeaderHeight;
+        }
+
+        if (horizontalScrollBar.isVisible()) {
+            height -= horizontalScrollBar.getHeight();
+        }
+
+        if (verticalScrollBar.isVisible()) {
+            width -= verticalScrollBar.getWidth();
+        }
+
+        return new Bounds(x, y, width, height);
+    }
+
+    // ScrollPaneListener methods
+
+    public void horizontalScrollBarPolicyChanged(ScrollPane scrollPane,
+        ScrollBarPolicy previousHorizontalScrollBarPolicy) {
+        invalidateComponent();
+    }
+
+    public void verticalScrollBarPolicyChanged(ScrollPane scrollPane,
+        ScrollBarPolicy previousVerticalScrollBarPolicy) {
+        invalidateComponent();
+    }
+
+    public void rowHeaderChanged(ScrollPane scrollPane, Component previousRowHeader) {
+        invalidateComponent();
+    }
+
+    public void columnHeaderChanged(ScrollPane scrollPane, Component previousColumnHeader) {
+        invalidateComponent();
+    }
+
+    public void cornerChanged(ScrollPane scrollPane, Component previousCorner) {
+        invalidateComponent();
+    }
+
+    // ViewportListener methods
+
+    public void scrollTopChanged(Viewport viewport, int previousScrollTop) {
+        // NOTE we don't invalidate the component here because we need only
+        // reposition the view and row header. Invalidating would yield
+        // the correct positioning, but it would do much more work than needed.
+
+        ScrollPane scrollPane = (ScrollPane)viewport;
+
+        Component view = scrollPane.getView();
+        Component columnHeader = scrollPane.getColumnHeader();
+        Component rowHeader = scrollPane.getRowHeader();
+
+        int scrollTop = scrollPane.getScrollTop();
+        int columnHeaderHeight = 0;
+
+        if (columnHeader != null) {
+            columnHeaderHeight = columnHeader.getHeight();
+        }
+
+        if (view != null) {
+            view.setLocation(view.getX(), columnHeaderHeight - scrollTop);
+        }
+
+        if (rowHeader != null) {
+            rowHeader.setLocation(0, columnHeaderHeight - scrollTop);
+        }
+
+        if (scrollTop >= 0 && scrollTop <= getMaxScrollTop()) {
+            verticalScrollBar.setValue(scrollTop);
+        }
+    }
+
+    public void scrollLeftChanged(Viewport viewport, int previousScrollLeft) {
+        // NOTE we don't invalidate the component here because we need only
+        // reposition the view and column header. Invalidating would yield
+        // the correct positioning, but it would do much more work than needed.
+
+        ScrollPane scrollPane = (ScrollPane)viewport;
+
+        Component view = scrollPane.getView();
+        Component columnHeader = scrollPane.getColumnHeader();
+        Component rowHeader = scrollPane.getRowHeader();
+
+        int scrollLeft = scrollPane.getScrollLeft();
+        int rowHeaderWidth = 0;
+
+        if (rowHeader != null) {
+            rowHeaderWidth = rowHeader.getWidth();
+        }
+
+        if (view != null) {
+            view.setLocation(rowHeaderWidth - scrollLeft, view.getY());
+        }
+
+        if (columnHeader != null) {
+            columnHeader.setLocation(rowHeaderWidth - scrollLeft, 0);
+        }
+
+        if (scrollLeft >= 0 && scrollLeft <= getMaxScrollLeft()) {
+            horizontalScrollBar.setValue(scrollLeft);
+        }
+    }
+
+    public void viewChanged(Viewport viewport, Component previousView) {
+        invalidateComponent();
+    }
+
+    // ScrollBarValueListener methods
+
+    public void valueChanged(ScrollBar scrollBar, int previousValue) {
+        ScrollPane scrollPane = (ScrollPane)getComponent();
+
+        int value = scrollBar.getValue();
+
+        if (scrollBar == horizontalScrollBar) {
+            scrollPane.setScrollLeft(value);
+        } else {
+            scrollPane.setScrollTop(value);
+        }
+    }
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SeparatorSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SeparatorSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SeparatorSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SeparatorSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,271 @@
+package pivot.wtk.skin;
+
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.font.FontRenderContext;
+import java.awt.font.LineMetrics;
+import java.awt.geom.Area;
+import java.awt.geom.Rectangle2D;
+
+import pivot.collections.Dictionary;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+import pivot.wtk.Insets;
+import pivot.wtk.Platform;
+import pivot.wtk.Separator;
+import pivot.wtk.SeparatorListener;
+import pivot.wtk.Theme;
+
+/**
+ * Separator skin.
+ *
+ * @author gbrown
+ */
+public class SeparatorSkin extends ComponentSkin
+    implements SeparatorListener {
+    private FontRenderContext fontRenderContext = new FontRenderContext(null, true, true);
+
+    private Font font;
+    private Color color;
+    private Color headingColor;
+    private int thickness;
+    private Insets padding;
+
+    public SeparatorSkin() {
+        Theme theme = Theme.getTheme();
+        font = theme.getFont().deriveFont(Font.BOLD);
+        color = Color.BLACK;
+        headingColor = Color.BLACK;
+        thickness = 1;
+        padding = new Insets(4, 0, 4, 4);
+    }
+
+    @Override
+    public void install(Component component) {
+        super.install(component);
+
+        Separator separator = (Separator)component;
+        separator.getSeparatorListeners().add(this);
+    }
+
+    @Override
+    public void uninstall() {
+        Separator separator = (Separator)getComponent();
+        separator.getSeparatorListeners().remove(this);
+
+        super.uninstall();
+    }
+
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+
+        Separator separator = (Separator)getComponent();
+        String heading = separator.getHeading();
+
+        if (heading != null
+            && heading.length() > 0) {
+            Rectangle2D headingBounds = font.getStringBounds(heading, fontRenderContext);
+            preferredWidth = (int)Math.ceil(headingBounds.getWidth())
+                + (padding.left + padding.right);
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight = thickness;
+
+        Separator separator = (Separator)getComponent();
+        String heading = separator.getHeading();
+
+        if (heading != null
+            && heading.length() > 0) {
+            LineMetrics lm = font.getLineMetrics(heading, fontRenderContext);
+            preferredHeight = Math.max((int)Math.ceil(lm.getAscent() + lm.getDescent()
+                + lm.getLeading()), preferredHeight);
+        }
+
+        preferredHeight += (padding.top + padding.bottom);
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        // TODO Optimize
+        return new Dimensions(getPreferredWidth(-1), getPreferredHeight(-1));
+    }
+
+    public void layout() {
+        // No-op
+    }
+
+    public void paint(Graphics2D graphics) {
+        Separator separator = (Separator)getComponent();
+        int width = getWidth();
+        int separatorY = padding.top;
+
+        String heading = separator.getHeading();
+
+        if (heading != null
+            && heading.length() > 0) {
+            LineMetrics lm = font.getLineMetrics(heading, fontRenderContext);
+
+            if (fontRenderContext.isAntiAliased()) {
+                graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
+                    Platform.getTextAntialiasingHint());
+            }
+
+            if (fontRenderContext.usesFractionalMetrics()) {
+                graphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
+                    RenderingHints.VALUE_FRACTIONALMETRICS_ON);
+            }
+
+            graphics.setFont(font);
+            graphics.setPaint(headingColor);
+            graphics.drawString(heading, padding.left, lm.getAscent() + padding.top);
+
+            Rectangle2D headingBounds = font.getStringBounds(heading, fontRenderContext);
+
+            Area titleClip = new Area(graphics.getClip());
+            titleClip.subtract(new Area(new Rectangle2D.Double(padding.left, padding.top,
+                headingBounds.getWidth() + padding.right, headingBounds.getHeight())));
+            graphics.clip(titleClip);
+
+            separatorY += (lm.getAscent() + lm.getDescent()) / 2 + 1;
+        }
+
+        graphics.setStroke(new BasicStroke(thickness));
+        graphics.setColor(color);
+        graphics.drawLine(0, separatorY, width, separatorY);
+    }
+
+    /**
+     * @return
+     * <tt>false</tt>; spacers are not focusable.
+     */
+    @Override
+    public boolean isFocusable() {
+        return false;
+    }
+
+    public Font getFont() {
+        return font;
+    }
+
+    public void setFont(Font font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        this.font = font;
+        invalidateComponent();
+    }
+
+    public final void setFont(String font) {
+        if (font == null) {
+            throw new IllegalArgumentException("font is null.");
+        }
+
+        setFont(Font.decode(font));
+    }
+
+    public Color getColor() {
+        return color;
+    }
+
+    public void setColor(Color color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        this.color = color;
+        repaintComponent();
+    }
+
+    public final void setColor(String color) {
+        if (color == null) {
+            throw new IllegalArgumentException("color is null.");
+        }
+
+        setColor(decodeColor(color));
+    }
+
+    public Color getHeadingColor() {
+        return headingColor;
+    }
+
+    public void setHeadingColor(Color headingColor) {
+        if (headingColor == null) {
+            throw new IllegalArgumentException("headingColor is null.");
+        }
+
+        this.headingColor = headingColor;
+        repaintComponent();
+    }
+
+    public final void setHeadingColor(String headingColor) {
+        if (headingColor == null) {
+            throw new IllegalArgumentException("headingColor is null.");
+        }
+
+        setHeadingColor(decodeColor(headingColor));
+    }
+
+    public int getThickness() {
+        return thickness;
+    }
+
+    public void setThickness(int thickness) {
+        this.thickness = thickness;
+        invalidateComponent();
+    }
+
+    public final void setThickness(Number thickness) {
+        if (thickness == null) {
+            throw new IllegalArgumentException("thickness is null.");
+        }
+
+        setThickness(thickness.intValue());
+    }
+
+    public Insets getPadding() {
+        return padding;
+    }
+
+    public void setPadding(Insets padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        this.padding = padding;
+        invalidateComponent();
+    }
+
+    public final void setPadding(Dictionary<String, ?> padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(new Insets(padding));
+    }
+
+    public final void setPadding(int padding) {
+        setPadding(new Insets(padding));
+    }
+
+    public final void setPadding(Number padding) {
+        if (padding == null) {
+            throw new IllegalArgumentException("padding is null.");
+        }
+
+        setPadding(padding.intValue());
+    }
+
+    // Separator events
+    public void headingChanged(Separator separator, String previousHeading) {
+        invalidateComponent();
+    }
+}
\ No newline at end of file

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SliderSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SliderSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SliderSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/SliderSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.wtk.skin;
+
+import pivot.wtk.Component;
+import pivot.wtk.Slider;
+import pivot.wtk.SliderListener;
+import pivot.wtk.SliderValueListener;
+
+/**
+ * Abstract base class for slider skins.
+ *
+ * @author gbrown
+ */
+public abstract class SliderSkin extends ContainerSkin
+	implements SliderListener, SliderValueListener {
+	@Override
+	public void install(Component component) {
+		super.install(component);
+
+		Slider slider = (Slider)component;
+		slider.getSliderListeners().add(this);
+		slider.getSliderValueListeners().add(this);
+	}
+
+	@Override
+	public void uninstall() {
+		Slider slider = (Slider)getComponent();
+		slider.getSliderListeners().remove(this);
+		slider.getSliderValueListeners().remove(this);
+
+		super.uninstall();
+	}
+
+	public void boundsChanged(Slider slider, int previousMinimum, int previousMaximum) {
+		invalidateComponent();
+	}
+
+	public void valueChanged(Slider slider, int previousValue) {
+		// No-op
+	}
+}

Added: incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/StackPaneSkin.java
URL: http://svn.apache.org/viewvc/incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/StackPaneSkin.java?rev=758461&view=auto
==============================================================================
--- incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/StackPaneSkin.java (added)
+++ incubator/pivot/branches/1.1/wtk/src/pivot/wtk/skin/StackPaneSkin.java Wed Mar 25 23:08:38 2009
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2008 VMware, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package pivot.wtk.skin;
+
+import pivot.wtk.StackPane;
+import pivot.wtk.Component;
+import pivot.wtk.Dimensions;
+
+/**
+ * Stack pane skin.
+ *
+ * @author gbrown
+ */
+public class StackPaneSkin extends ContainerSkin {
+    public int getPreferredWidth(int height) {
+        int preferredWidth = 0;
+        StackPane stackPane = (StackPane)getComponent();
+
+        for (Component component : stackPane) {
+            preferredWidth = Math.max(preferredWidth,
+                component.getPreferredWidth(height));
+        }
+
+        return preferredWidth;
+    }
+
+    public int getPreferredHeight(int width) {
+        int preferredHeight = 0;
+        StackPane stackPane = (StackPane)getComponent();
+
+        for (Component component : stackPane) {
+            preferredHeight = Math.max(preferredHeight,
+                component.getPreferredHeight(width));
+        }
+
+        return preferredHeight;
+    }
+
+    public Dimensions getPreferredSize() {
+        int preferredWidth = 0;
+        int preferredHeight = 0;
+
+        StackPane stackPane = (StackPane)getComponent();
+
+        for (Component component : stackPane) {
+            Dimensions preferredCardSize = component.getPreferredSize();
+
+            preferredWidth = Math.max(preferredWidth,
+                preferredCardSize.width);
+
+            preferredHeight = Math.max(preferredHeight,
+                preferredCardSize.height);
+        }
+
+        return new Dimensions(preferredWidth, preferredHeight);
+    }
+
+    public void layout() {
+        // Set the size of all components to match the size of the stack pane
+        StackPane stackPane = (StackPane)getComponent();
+
+        int width = getWidth();
+        int height = getHeight();
+        for (Component component : stackPane) {
+            component.setSize(width, height);
+        }
+    }
+}