Creating the iTunes navigation header button

December 2, 2009


As promised, here is the code to create the iTunes navigation header button. It’s not a perfect replica, but it’s as close as is practical.

iTunes uses hand drawn artwork, which is not easy to replicate in code. The inner shadows, for example, are simulated in the my code and look decent, but are not a perfect facsimile of the original. These subtle details are almost invisible when you look at the component, but without these details, the component looks cheap and amateurish.

Some highlights of what’s going on in the code:

  • Inner shadow simulation on the left, top and bottom sides of the selected button.
  • Upward pointing shadow under the text.

See the comments in the code provide further explanation of these items. To actually create the iTunes navigation header component, you can adapt the code from my last post, with TriAreaComponent.

public class ITunesHeaderButtonUI extends BasicButtonUI {

    private static Color TEXT_COLOR = Color.WHITE;
    private static Color TEXT_SHADOW_COLOR = Color.BLACK;

    // the gradient colors for when the button is selected.
    private static Color SELECTED_BACKGROUND_COLOR_1 = new Color(0x141414);
    private static Color SELECTED_BACKGROUND_COLOR_2 = new Color(0x1e1e1e);
    private static Color SELECTED_BACKGROUND_COLOR_3 = new Color(0x191919);
    private static Color SELECTED_BACKGROUND_COLOR_4 = new Color(0x1e1e1e);

    // the border colors for the button.
    private static Color SELECTED_TOP_BORDER = new Color(0x030303);
    private static Color SELECTED_BOTTOM_BORDER = new Color(0x292929);

    // the border colors between buttons.
    private static Color LEFT_BORDER = new Color(255,255,255,21);
    private static Color RIGHT_BORDER = new Color(0,0,0,125);

    private static final Color SELECTED_INNER_SHADOW_COLOR_1 = new Color(0x161616);
    private static final Color SELECTED_INNER_SHADOW_COLOR_2 = new Color(0x171717);
    private static final Color SELECTED_INNER_SHADOW_COLOR_3 = new Color(0x191919);

    @Override
    protected void installDefaults(AbstractButton button) {
        super.installDefaults(button);
        button.setBackground(new Color(0,0,0,0));
        button.setOpaque(false);
    }

    @Override
    public void paint(Graphics g, JComponent c) {
        // if the button is selected, paint the special background now.
        // if it is not selected paint the left and right highlight border.
        AbstractButton button = (AbstractButton) c;
        if (button.isSelected()) {
            paintButtonSelected(g, button);
        } else {
            // paint the border and border highlight if the button isn't
            // selected.
            g.setColor(LEFT_BORDER);
            g.drawLine(0, 1, 0, button.getHeight()-2);
            g.setColor(RIGHT_BORDER);
            g.drawLine(button.getWidth()-1, 1,
                    button.getWidth()-1, button.getHeight()-2);
        }

        super.paint(g, c);
    }

    @Override
    protected void paintText(Graphics g, AbstractButton button,
                             Rectangle textRect, String text) {
        // we need to override the paintText method so that we can paint
        // the text shadow. the paintText method in BasicButtonUI pulls
        // the color to use from the foreground property -- there is no
        // way to change this during the painting process without causing
        // an infinite sequence of events, so we must implement our own 
        // text painting.

        FontMetrics fontMetrics = g.getFontMetrics(button.getFont());
        int mnemonicIndex = button.getDisplayedMnemonicIndex();

        // paint the shadow text.
        g.setColor(TEXT_SHADOW_COLOR);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
                textRect.x + getTextShiftOffset(),
                textRect.y + fontMetrics.getAscent() + getTextShiftOffset() - 1);

        // paint the actual text.
        g.setColor(TEXT_COLOR);
        BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
                textRect.x + getTextShiftOffset(),
                textRect.y + fontMetrics.getAscent() + getTextShiftOffset());
    }

    /**
     * Paints the selected buttons state, also used as the pressed state.
     */
    private void paintButtonSelected(Graphics graphics, AbstractButton button) {
        // calculate the middle of the area to paint.
        int midY = button.getHeight()/2;

        Paint topPaint = new GradientPaint(0, 0, SELECTED_BACKGROUND_COLOR_1,
                0, midY, SELECTED_BACKGROUND_COLOR_2);
        ((Graphics2D) graphics).setPaint(topPaint);
        graphics.fillRect(0, 0, button.getWidth(), midY);

        // paint the top half of the background with the corresponding
        // gradient.
        Paint bottomPaint =
                new GradientPaint(0, midY + 1, SELECTED_BACKGROUND_COLOR_3,
                        0, button.getHeight(), SELECTED_BACKGROUND_COLOR_4);
        ((Graphics2D) graphics).setPaint(bottomPaint);
        graphics.fillRect(0, midY, button.getWidth(), button.getHeight());

        // draw the top and bottom border.
        graphics.setColor(SELECTED_TOP_BORDER);
        graphics.drawLine(0, 0, button.getWidth(), 0);
        graphics.setColor(SELECTED_BOTTOM_BORDER);
        graphics.drawLine(0, button.getHeight() - 1,
                button.getWidth(), button.getHeight() - 1);

        // paint the outter part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_1);
        graphics.drawLine(0, 1, 0, button.getHeight()-2);
        graphics.drawLine(0, 1, button.getWidth(), 1);
        graphics.drawLine(button.getWidth()-1, 1,
                button.getWidth()-1, button.getHeight()-2);

        // paint the middle part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_2);
        graphics.drawLine(1, 1, 1, button.getHeight()-2);
        graphics.drawLine(0, 2, button.getWidth(), 2);
        graphics.drawLine(button.getWidth()-2, 1,
                button.getWidth()-2, button.getHeight()-2);

        // paint the inner part of the inner shadow.
        graphics.setColor(SELECTED_INNER_SHADOW_COLOR_3);
        graphics.drawLine(2, 1, 2, button.getHeight()-2);
        graphics.drawLine(0, 3, button.getWidth(), 3);
        graphics.drawLine(button.getWidth()-3, 1,
                button.getWidth()-3, button.getHeight()-2);
    }

    @Override
    protected void paintButtonPressed(Graphics graphics, AbstractButton button) {
        paintButtonSelected(graphics, button);
    }
}

7 Responses to “Creating the iTunes navigation header button”


  1. […] This post was Twitted by javafxarticles […]


  2. […] Orr, shortly before announcing he is heading to work at Apple, posted about how you can recreate the iTunes navigation header button using Java2D and announced the availability of the Sea Glass Look and Feel 0.1 […]

  3. Tristan Seifert Says:

    When are you going to implement the Pop-down iTUnes style button, that slides out a menu when you hover over it and when you click it? And if you do not have time for macwidgets anymore, I’d take it over, if you’d like.

    • Ken Says:

      Hi Tristan,

      If you’d like to provide a sample implementation of the component you mention, that’d be much appreciated.

      I’m still not sure what’s going to happen with Mac Widgets for Java, but I’ll keep your offer in mind.

      -Ken

  4. Tristan Seifert Says:

    OK. I’ll work on that. Maybe you should put the iTunes components into MacWidgets?


  5. […] faire des barre de header à la iTunes , faire des listes au look Apple bref que du bon. […]


Leave a comment