The Source for Java Technology Collaboration
User: Password:



Tim Boudreau

Tim Boudreau's Blog

JNN just got prettier (at least on the mac)

Posted by timboudreau on February 04, 2005 at 11:26 AM | Comments (7)

Having a couple evenings to kill in a hotel room, and needing to do a bit of coding to keep myself sane, I wrote some UI and keyboard usability improvements to JNN, James Gosling's RSS reader (screenshot in blog). I hope you'll agree the results are pretty slick.

I got involved in JNN a year ago, when I downloaded it, and noticed it had some "border build-up" problems in the UI - it was a little ugly. And since I'd been doing some similar fixes for NetBeans, I spent a Saturday afternoon putting together some patches, and ended up being a committer on the project.

JNN is just a handy RSS reader - it's become sort of my news source when I'm online. And I'd done a bunch of work over the last year making NetBeans look really nice on the macintosh, so I figured I'd apply those skills to JNN and make it seem a little more at home on the macintosh desktop. Here's a screenshot (yes, this is a pure Java app!):

jnn.png

It's not quite as pretty on Windows or Linux yet - but if I can tear myself away from my mac, it should be possible to do something similar there - or maybe someone else will. Some of the inspiration for the shadows and the color of the RSS list came from iTunes.

I mentioned improvements in keyboard navigability - here's what's new:

  • Ctrl (Command on mac) - up/down will navigate RSS feeds - no need to tab to the list
  • Up/Down arrows navigate messages even if keyboard focus is in the message area
  • Enter from anywhere will open the top link of the current message in a browser - no mouse needed
  • +/- will adjust the font size

Now for how the shadows and rounded outlines work: Only two things actually changed in the UI - using the system property apple.awt.brushMetalLook to set the main window background, and using a custom Border class on the mac. The painting is all Java2D to do the shadows and such. The shadows are simply taking a RoundedRectangle2D.Double and painting it repeatedly with lighter colors, while setting the clipping area so it doesn't paint at the bottom of the rounded rectangle - and then doing the same thing for the bottom with lighter colors. The point being that getting these nice effects is really quite simple - here's the painting code that does it:

    private static final int ARC = 22;

    public void paintBorder(Component component, Graphics g, int a,
                                    int b, int c, int d) {

        Graphics2D g2d = (Graphics2D) g;
        RoundRectangle2D.Double r =
            new RoundRectangle2D.Double (a+(ARC / 2), b+(ARC/2), c-ARC, d-ARC,
            ARC, ARC);

        if (component instanceof JScrollPane) {
            component = ((JScrollPane) component).getViewport().getView();
        }
        Color bg = component.getBackground();

        Color outer = UIManager.getColor("control");

        g2d.setRenderingHints(HINTS);

        g2d.setColor (outer);
        g2d.fillRect (a, b, c, d);

        g2d.setPaint (bg);
        g2d.fill(r);

        Shape clip = g2d.getClip();

        Rectangle withoutBottom = new Rectangle (a, b, c, d - ARC);

        if (clip != null) {
            Area area = new Area (clip);
            area.intersect(new Area(withoutBottom));
            g.setClip (area);
        } else {
            g.setClip (withoutBottom);
        }

        double amt = 0.65d;
        int blu = 5;

        int[] colors = new int[] { 125, 160, 180, 215, 232, 242 };
        int red = bg.getRed();
        int green = bg.getGreen();
        int blue = bg.getBlue();
        double ttl = 0;
        Color first = null;
        for (int i=0; i < colors.length; i++) {
            g2d.translate (0d, amt);
            ttl += amt;
            int ra = red - (255 - colors[i]);
            int ga = green - (255 - colors[i]);
            int ba = blue - (255 - colors[i]) + blu;
            Color col = new Color (ra, ga, ba);
            g2d.setPaint (col);
            g2d.draw(r);
            if (i == colors.length /2) {
                first = col;
            }
        }
        g2d.translate (0d, -ttl);

        Rectangle bottom = new Rectangle (a, b + (d - ARC), c, ARC/2);

        if (clip != null) {
            Area area = new Area (clip);
            area.intersect(new Area(bottom));
            g.setClip (area);
        } else {
            g.setClip (bottom);
        }
        g2d.translate (0, -amt);
        g2d.setPaint (new GradientPaint (0, (b+d)-ARC, first, 0, b+(d-(ARC/2)), Color.WHITE));
        g2d.draw(r);

        Composite comp = g2d.getComposite();
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f));

        g2d.translate (0, -amt);
        g2d.setPaint (new GradientPaint (0, (b+d)-ARC, first, 0, b+(d-(ARC/2)), Color.WHITE));
        g2d.draw(r);

        g2d.setClip (clip);
        g2d.translate (0, amt * 2);
        g2d.setComposite (comp);
    }

    private static final Map HINTS = new HashMap();
    static {
        HINTS.put (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    }

Bookmark blog post: del.icio.us del.icio.us Digg Digg DZone DZone Furl Furl Reddit Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment

  • Oh yes Tim, I will have to agree, these are some mighty fine UI improvements.
    One question:
    Is the toolbar supposed to be movable, when I grab it, it makes the usual xor outline, but it doesn’t move?
    Better post an image...look at the left side
    of the Image.

    [click to View BIG>]

    Posted by: bakbjo on February 05, 2005 at 12:11 AM

  • The toolbar is not supposed to be movable - the drag grip and all are turned off, at least on the mac.

    Posted by: timboudreau on February 05, 2005 at 09:59 AM

  • That's cool ! I recently implemented the same thing with borders too (see my SearchField Demo screenshot). Yet, I used Arc2D.Double instead of a rounded rectangle with clipping. I have to admit I prefer your solution. Arc2Ds are difficult to handle properly for such a job :(

    Posted by: gfx on February 07, 2005 at 10:15 AM

  • Hi Tim,

    I'm running JNN on Windows XP, Jdk 1.4.2_06 and i've got the following stacktrace :

    C:\Applications>C:\j2sdk1.4.2_06\bin\java -jar jnn.jar
    No speech synthesizer available.
    java.lang.NoClassDefFoundError: com/sun/labs/tools/blog/PropertyDialog
    at com.sun.tools.jnn.RSSBrowser.loadProxyConfig(RSSBrowser.java:1316)
    at com.sun.tools.jnn.RSSBrowser.run(RSSBrowser.java:327)
    at java.lang.Thread.run(Thread.java:534)


    The application starts normally but an annoying busy cursor is displayed on screen forever. I can't add any RSS feed at all, there's an image with a "working man " displayed each time i try drag and dropping any RSS feed.

    Furthermore, did you add support for proxies (including those requiring authentication)?


    Gilles Philippart

    Posted by: gphilipp on February 08, 2005 at 01:42 AM

  • Is there a way to learn this slowly?
    Like how to write a hello world panel
    with just a filed and a button and then the fancy 2d collo stuff?

    a link?
    .V

    Posted by: netsql on February 11, 2005 at 06:50 AM

  • You could basically just take the above code, put it in a subclass of Border, and set that border on whatever.

    Posted by: timboudreau on April 07, 2005 at 08:39 AM

  • Giles -- a little late to the game, but check out https://bloged.dev.java.net/ for com/sun/labs/tools/blog/PropertyDialog, I believe.

    Posted by: rufwork on August 27, 2007 at 07:04 PM





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds