Synth subtleties

Synth can sometimes be… very tricky to use. This is not necessarily Synth's fault by the way, but rather because of Swing internals. Hence, I would like to share with you something difficult to get the first time you encounter it.

Skinning a scrollbar is quite easy with Synth. A scrollbar is made of the scrollbar itself, two arrow buttons, the thumb and the track. You can see the scrollbar as a container for the buttons, the thumb and the track. Here is a first attempt to skin a JScrollBar with Synth:

<!-- ================================= -->
<!-- SCROLLBAR ARROWS -->
<!-- ================================= -->
<style id="scrollBarArrowStyle">
    <state>
        <imagePainter method="arrowButtonForeground" path="images/scrollBar-up.png" center="true" direction="north" />
        <imagePainter method="arrowButtonForeground" path="images/scrollBar-down.png" center="true" direction="south" />
        <imagePainter method="arrowButtonForeground" path="images/scrollBar-left.png" center="true" direction="west" />
        <imagePainter method="arrowButtonForeground" path="images/scrollBar-right.png" center="true" direction="east" />
    </state>
</style>
<bind style="scrollBarArrowStyle" type="REGION" key="ArrowButton" />

<!-- ================================= -->
<!-- SCROLLBAR THUMB -->
<!-- ================================= -->
<style id="scrollBarThumbStyle">
    <state>
        <imagePainter method="scrollBarThumbBackground" direction="horizontal" path="images/scrollBar-thumb-horizontal.png" sourceInsets="0 7 0 7" />
        <imagePainter method="scrollBarThumbBackground" direction="vertical" path="images/scrollBar-thumb-vertical.png" sourceInsets="7 0 7 0" />
        <imagePainter method="scrollBarThumbBackground" direction="horizontal" path="images/scrollBar-thumb-horizontal-grip.png" center="true" />
        <imagePainter method="scrollBarThumbBackground" direction="vertical" path="images/scrollBar-thumb-vertical-grip.png" center="true" />
    </state>
    
    <state value="MOUSE_OVER">
        <imagePainter method="scrollBarThumbBackground" direction="horizontal" path="images/scrollBar-thumb-horizontal-mouseover.png" sourceInsets="0 7 0 7" />
        <imagePainter method="scrollBarThumbBackground" direction="vertical" path="images/scrollBar-thumb-vertical-mouseover.png" sourceInsets="7 0 7 0" />
        <imagePainter method="scrollBarThumbBackground" direction="horizontal" path="images/scrollBar-thumb-horizontal-grip.png" center="true" />
        <imagePainter method="scrollBarThumbBackground" direction="vertical" path="images/scrollBar-thumb-vertical-grip.png" center="true" />
    </state>

    <state value="DISABLED">
    </state>
</style>
<bind style="scrollBarThumbStyle" type="REGION" key="ScrollBarThumb" />

<!-- ================================= -->
<!-- SCROLLBAR TRACK -->
<!-- ================================= -->
<style id="scrollbarTrackStyle">
    <state>
        <imagePainter method="scrollBarTrackBackground" path="images/scrollBar-track-horizontal.png" direction="horizontal" sourceInsets="0 11 0 11" />
        <imagePainter method="scrollBarTrackBackground" path="images/scrollBar-track-vertical.png" direction="vertical" sourceInsets="11 0 11 0" />
    </state>
    <state value="DISABLED">
        <imagePainter method="scrollBarTrackBackground" path="images/scrollBar-track-horizontal.png" direction="horizontal" sourceInsets="0 11 0 11" />
        <imagePainter method="scrollBarTrackBackground" path="images/scrollBar-track-vertical.png" direction="vertical" sourceInsets="11 0 11 0" />
    </state>
</style>
<bind style="scrollbarTrackStyle" type="REGION" key="ScrollBarTrack" />

Nothing really fancy here but here is the result:

It doesn't look that good. The first thing we can fix here are the arrow buttons which are clipped on this screenshot. By default, Swing consider that the arrow buttons have a size of 16 pixels. In my case this doesn't work since my pictures have a dimension of 21×16 for the arrows. The first step in our fix is to specify the new size of the arrow buttons:

<style id="scrollBarArrowStyle">
  <property key="ArrowButton.size" type="integer" value="21" />
  ...
</style>

The result of this fix is the following:

We can now almost see the entire picture for the arrows. Take a look, for instance, at the left arrow of an horizontal scrollbar. Notice the left border and the smooth transition with the small curve just before the track. You should also understand now that the property ArrowButton.size defines a dimension specific to the orientation of the scrollbar. Hence, with ArrowButton.size=21, the width of the arrow button will be 21 pixels for an horizontal scrollbar, but the height of the button will be 21 pixels for a vertical scrollbar. It doesn't solve all our problems though.

To display the arrow buttons properly, we need to change the height of the thumb. This will force the scrollbar to get a given height, which will propagate to the arrow buttons. A property called ScrollBar.thumbHeight can be changed for that matter in the scrollbar style itself:

<style id="scrollBarStyle">
  <property key="ScrollBar.thumbHeight" type="integer" value="16" />
</style>
<bind style="scrollBarStyle" type="REGION" key="ScrollBar" />

The result of this fix is the following:

The arrow buttons are now perfect but the thumbs, at least the vertical one, are still not quite right. Hopefully another property, ScrollBar.minimumThumbSize, let us set a minimum size for the thumbs. As my thumbs pictures are 16 pixels tall for the horizontal one and 16 pixes wide for the vertical one, I wrote the following in scrollBarStyle:

<property key="ScrollBar.minimumThumbSize" type="Dimension" value="34 16" />

This property is a dimension and I used 34×16 to make the horizontal thumb be 34 pixels wide and 16 pixels tall as on the following screenshot. I also thought it would just swap the sizes for the vertical thumb (thus becoming 16×34) but then I saw this:

Very strange indeed. The horizontal scrollbar is now perfect but the vertical one is still not right. It turns out the ScrollBar.minimumThumbSize property does not define the dimension of one thumb. In fact the width of this property defines the width of the horizontal thumb and its height the height of the vertical thumb. Here is what you should write::

<property key="ScrollBar.minimumThumbSize" type="Dimension" value="34 34" />

This means you need to use both ScrollBar.minimumThumbSize and ScrollBar.thumbHeight to actually set the correct size of the thumbs. Using these properties is really tricky because ScrollBar.thumbHeight will be used both as a width (vertical scrollbar) and as a height (horizontal scrollbar) wheras
ScrollBar.minimumThumbSize values are applied to two different components rather than two attributs of one component.

Here is our final result:

If you are planning to work with Synth, remember Synth skins map directly to Swing. They are not always easy to use because you have to understand how some parts of Swing work. Take the example of a JTable: to skin the cells you'll have to bind a style to JLabel because JTable's default cell renderer inherits from JLabel. In the end, a lot of weird behaviors you might encounter and think of as bugs are just due to interal plumbing subtelties.

I'll will shortly publish a serie of articles about Swing components skinning with Synth. I hope this will help you to create great looking Synth skins without going crazy and/or mad at us :)

2 Responses to “Synth subtleties”

  1. Wundt says:

    I have the problem that my scroll bars aren’t registering the PRESSED value…? It this a known problem? Is there a work around?