Skinning a scroll bar – Part 1

December 8, 2008

It’s true, there really is more than one way to skin a scrollbar (sorry, couldn’t resist). I’ve spent the last month of my spare time (wow that sounds lame) experimenting with the best way to allow developers to provide the minimum amount of code in order to specify a unique look (skin) for a scroll bar.

From the outset, I wanted to eliminate the mostly redundant code that must be included in a ScrollBarUI. Have you ever tried writing a custom scroll bar UI delegate? It’s not much fun. Creating anything but the most boring ScrollBarUI requires a painful discovery of how BasicScrollBarUI works. I think you’ll find the following skinning infrastructure greatly simplifies the customization of JScrollBars.

scroll_bars1

In this first part of the “Skinning a scroll bar” series, we’ll look at the core enumeration that eliminates much of the redundant code that must be included in a ScrollBarUI.

When you first look at BasicScrollBarUI you’ll notice that many of the methods have conditional logic based on the scroll bar’s orientation (either horizontal or vertical). This switching bloats the code and makes it hard to read and understand.

After staring at this code for many evenings I came up with the following enumeration that abstracted out the methods needed for all layout and painting calculations. As annotated in the artwork above, there are two dimensions of concern that remain constant regardless of the orientation, namely the scrolling dimension and the thickness dimension.

The concrete axis that these dimensions map to changes, of course, with the orientation of the scroll bar – this is what the enumeration hides for us. The scrolling dimension maps to the x axis for a horizontal scroll bar, while the scroll dimension maps to the y axis for a vertical scroll bar. None of the actual calculations in the UI delegate care about the actual axis.

Below you’ll find the enumeration I came up with, which gives access to axis-agnostic values, like position, length and thickness.

public enum ScrollBarOrientation {

    HORIZONTAL {
        int getThickness(Dimension size) {
            return size.height;
        }
        int getLength(Dimension size) {
            return size.width;
        }
        int getPosition(Point point) {
            return point.x;
        }
        Rectangle updateBoundsPosition(Rectangle bounds, int newPosition) {
            bounds.setLocation(newPosition, bounds.y);
            return bounds;
        }
        Rectangle createBounds(Component container, int position, int length) {
            return new Rectangle(position, 0, length, container.getHeight());
        }
        Rectangle createCenteredBounds(Component container, int position, int thickness, int length) {
            int y = container.getHeight() / 2 - thickness / 2;
            return new Rectangle(position, y, length, thickness);
        }
    },

    VERTICAL {
        int getThickness(Dimension size) {
            return size.width;
        }
        int getLength(Dimension size) {
            return size.height;
        }
        int getPosition(Point point) {
            return point.y;
        }
        Rectangle updateBoundsPosition(Rectangle bounds, int newPosition) {
            bounds.setLocation(bounds.x, newPosition);
            return bounds;
        }
        Rectangle createBounds(Component container, int position, int length) {
            return new Rectangle(0, position, container.getWidth(), length);
        }
        Rectangle createCenteredBounds(Component container, int position, int thickness, int length) {
            int x = container.getWidth() / 2 - thickness / 2;
            return new Rectangle(x, position, thickness, length);
        }
    };

    /**
     * Get's the thickness of the given size. Thickness corresponds to the dimension that does not
     * vary in size. That is, a horizontal scroll bar's thickness corresponds to the y dimension,
     * while a vertical scroll bar's thickness corresponds to the x dimension.
     *
     * @param size the 2-dimensional size to extract the thickness from.
     * @return the thickness of the given size.
     */
    abstract int getThickness(Dimension size);

    /**
     * Get's the length of the given size. Length corresponds to the dimension that varies in size.
     * That is, a horizontal scroll bar's length corresponds to the x dimension, while a vertical
     * scroll bar's length corresponds to the y dimension.
     *
     * @param size the 2-dimensional size to extract the length from.
     * @return the length of the given size.
     */
    abstract int getLength(Dimension size);

    /**
     * Get's the position from the given {@link Point}. Position refers to the dimension of a point
     * on which the scroll bar scrolls. That is, a horiztonal scroll bar's position corresponds to
     * the x dimension, while a vertical scroll bar's position corresponds to the y dimension.
     *
     * @param point the {@code Point} from which to extrac the position from.
     * @return the position value of the given {@code Point}.
     */
    abstract int getPosition(Point point);

    /**
     * Moves the given bounds to the given position. For a horiztonal scroll bar this translates
     * into {@code bounds.x = newPosition}, while for a vertical scroll bar this translates into
     * {@code bounds.y = newPosition}.
     *
     * @param bounds      the bounds to update with the new position.
     * @param newPosition the new position to set the bounds to.
     * @return the updated bounds.
     */
    abstract Rectangle updateBoundsPosition(Rectangle bounds, int newPosition);

    /**
     * Creates bounds based on the given {@link Component}, position and length. The supplied
     * component will be used to determine the thickness of the bounds. The position will be used
     * to locate the bounds along the scrollable axis. The length will be used to determine the
     * length of the bounds along the scrollable axis.
     * Horizontal scroll bars, the bounds will be derived like this:
     *     new Rectangle(position, 0, length, container.getHeigth())
     * Vertical scroll bar bounds will be derived like this:
     *     new Rectangle(0, container.getWidth(), position, length)
     *
     * @param container the {@code Component} to use to determine the thickness of the bounds.
     * @param position  the position of the bounds.
     * @param length    the length of the bounds.
     * @return the created bounds.
     */
    abstract Rectangle createBounds(Component container, int position, int length);

    /**
     * Creates bounds centered in the given {@link Component} located at the given position, with
     * the given thickness and length.
     * Horizontal scroll bars, the bounds will be derived like this:
     *       new Rectangle(position, container.getHeight()/2 - thickness/2, length, thickness)
     * Vertical scroll bars, the bounds will be derived like this:
     *     new Rectangle(container.getWidth()/2 - thickness/2, position, thickness, length)
     *
     * @param container the {@code Component} to use to determine the thickness of the bounds.
     * @param position  the position of the bounds.
     * @param thickness the thickness of the given bounds.
     * @param length    the length of the bounds.
     * @return the created bounds.
     */
    abstract Rectangle createCenteredBounds(Component container, int position, int thickness, int length);

}

In the next entry, we’ll see how this enumeration is used by the ScrollBarSkin.

6 Responses to “Skinning a scroll bar – Part 1”


  1. […] Ken Orr keeps the promise and has started a series on skinning the scroll bar component. […]


  2. […] last week’s entry on skinning the JScrollBar component, Ken Orr laments the lack of visual tools for assisting the creation of Swing look-and-feels and […]


  3. […] 29, 2008 In the first part of the “Skinning a scroll bar” series, I showed the ScrollBarOrientation class, which […]


  4. […] 18, 2009 In part one we looked at the ScrollBarOrientation class, where we saw how abstracting away the actual scrolling […]


  5. […] framework via an extension of BasicScrollBarUI. For more context on this series, see parts one and […]


  6. […] complicated process of creating a custom scroll bar UI delegate (which I talked about in detail in part 1, part 2, and part 3 of the “Skinning a scroll bar” […]


Leave a comment