Sea Glass version 0.1

December 4, 2009

Sea Glass version 0.1 is ready for you to try! This is a very preliminary version of the look and feel, and is missing artwork for tabs and component focused states. Also note that the artwork will likely change as we move forward based on your feedback.

Don’t expect rendering of components to be perfect in this release — text alignment, for example, might not be quite right. The goal with this very early preview is to get feedback on fundamental aspects of the look and feel (e.g. colors).

You can download seaglass-0.1.jar or check out the Google Code Download page.

You can download the demo (based on Laffy) here.

JavaOne 2010?

December 3, 2009

Anyone care to venture a guess as to whether there will be a JavaOne 2010? I know it’s pointless to speculate, but it sure is fun! Here are some facts to inform your guess:

Conference Year Call for Papers Conference Dates
2008 Oct. 26th – Nov. 16th, 2007 May 6th – 9th, 2008
2009 Nov. 19th – Dec. 19th, 2008 June 2nd – 5th, 2009
2010 ? June 22nd – 25th, 2010

According to the Moscone Center’s website, JavaOne 2010 has been scheduled for June 22nd – 25th, 2010. There hasn’t been a call for papers yet, but maybe that’s because the conference is a little later than usual.

What do you think?


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);
    }
}

Sea Glass artwork update

November 23, 2009

I haven’t had a whole lot of time recently, but I’ve updated the color palette (see the original colors back here) and drawn some new artwork for the Sea Glass look and feel. Kathryn has been plugging away on the implementation, working hard towards an initial release, which is still a ways off.

I’ll let the images do the rest of the talking!






Note that we haven’t settled on the color scheme for the Windows window chrome — suggestions and requests are welcome.

MacGraPhoto app bundle

November 19, 2009


I’m not usually into those Mac application bundles (like MacHeist) because they tend to include a bunch of unrelated stuff I don’t want along with one app that I do want.

The MacGraPhoto app bundle is a little different though. It includes 7 applications related to image editing, a couple of which received Apple Design Awards. Below are the two apps that make this bundle absolutely worth it to me:

DrawIt

Can anyone say “gorgeous”? Not only is this app beautiful, but it’s also snappy and simple to use. DrawIt will let you create vector art and then export it to a format of your choosing. This app is an absolute must for anyone drawing icons.


Picturesque


Picturesque is a simple app, with very targeted functionality. It will let you transform images in various ways, like adding perspective, reflections and drop shadows. For me, this app stream lines my image editing workflow when I want to adjust images for blog posts or presentations.

A bundle worth buying!


Thanks to Luke Nezda, Mac Widgets for Java now has a pom.xml file to work with Maven. Luke’s looking to get the pom file integrated into java.net’s Maven repository. Until then, you’ll have to download it manually.

itunes_navigation_barRecently, someone asked me what the best way to create the iTunes navigation header (seen in the iTunes music store — the black shiny bar at the top) would be. Here was my response:

The navigation header’s most prominent feature is it’s multi-stop gradient. There are four colors used in the gradient as illustrated below:

itunes_navigation_bar_gradient

But to really capture the subtleties of the navigation header we need to look closer at the top and bottom of the component, where you’ll notice an inner shadow, and an inner glow. The inner shadow and inner glow are what make the component visually interesting.

itunes_navigation_bar_inner_shadows

I chose to hard code the inner shadow and inner glow sizes to 3 pixels (top) and 2 pixels (bottom) rather than produce the real effects. Real inner shadows and glows aren’t straight forward to create (I talked about them here), and are computationally expensive to recompute because they aren’t currently computed on the graphics card. So I decided to hard code the inner shadow and inner glow colors to the exact colors seen in iTunes. You could figure out their grayscale and alpha values to make them reusable — I’ll leave that as an exercise for the reader.

Here’s the code:

public class ITunesNavigationHeader extends JComponent {

    // the hard-coded preferred height. ideally this would be derived
    // from the font size.
    private static int HEADER_HEIGHT = 25;

    // the background colors used in the multi-stop gradient.
    private static Color BACKGROUND_COLOR_1 = new Color(0x393939);
    private static Color BACKGROUND_COLOR_2 = new Color(0x2e2e2e);
    private static Color BACKGROUND_COLOR_3 = new Color(0x232323);
    private static Color BACKGROUND_COLOR_4 = new Color(0x282828);

    // the color to use for the top and bottom border.
    private static Color BORDER_COLOR = new Color(0x171717);

    // the inner shadow colors on the top of the header.
    private static Color TOP_SHADOW_COLOR_1 = new Color(0x292929);
    private static Color TOP_SHADOW_COLOR_2 = new Color(0x353535);
    private static Color TOP_SHADOW_COLOR_3 = new Color(0x383838);

    // the inner shadow colors on the bottom of the header.
    private static Color BOTTOM_SHADOW_COLOR_1 = new Color(0x2c2c2c);
    private static Color BOTTOM_SHADOW_COLOR_2 = new Color(0x363636);

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(-1, HEADER_HEIGHT);
    }

    @Override
    protected void paintComponent(Graphics g) {
        Graphics2D graphics = (Graphics2D) g.create();

        // calculate the middle of the area to paint.
        int midY = getHeight()/2;

        // paint the top half of the background with the corresponding
        // gradient. note that if we were using Java 6, we could use a
        // LinearGradientPaint with multiple stops.
        Paint topPaint = new GradientPaint(0, 0, BACKGROUND_COLOR_1,
                0, midY, BACKGROUND_COLOR_2);
        graphics.setPaint(topPaint);
        graphics.fillRect(0, 0, getWidth(), midY);

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

        // draw the top inner shadow.
        graphics.setColor(TOP_SHADOW_COLOR_1);
        graphics.drawLine(0, 1, getWidth(), 1);
        graphics.setColor(TOP_SHADOW_COLOR_2);
        graphics.drawLine(0, 2, getWidth(), 2);
        graphics.setColor(TOP_SHADOW_COLOR_3);
        graphics.drawLine(0, 3, getWidth(), 3);

        // draw the bottom inner shadow.
        graphics.setColor(BOTTOM_SHADOW_COLOR_1);
        graphics.drawLine(0, getHeight() - 3, getWidth(), getHeight() - 3);
        graphics.setColor(BOTTOM_SHADOW_COLOR_2);
        graphics.drawLine(0, getHeight() - 2, getWidth(), getHeight() - 2);

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

        graphics.dispose();
    }
}