A Swing User Interface

Today is a great day… I am done with school! I will finally get my M.S. in Computer Science. Today was also the day we finished our final assignment. Our goal was to write some sort of a component-oriented/network-distributed VNC clone. I ended up in charge of the UI that I had to crank out in very little time. Just for fun, here are some pictures from that application.

The following picture shows the mockup I did in Photoshop. Forget about that “Pause” icon, it sucks but I got lazy and didn’t want to find a better one. And yes, I did borrowed everything from various Mac OS X apps (the tabs come from Transmit and the slider from iPhoto.)

SuperVNC

Next, here is a screenshot of the actual application running on Windows XP. Despite the Aqua-like components, the design works pretty well. It took me by surprise.

SuperVNC

My only problem with this UI is that I couldn’t find an easy way to get a unified toolbar, as show in the mockup. While the toolbar looks fine on Windows, it looks a bit weird on Mac OS X, as shown below. The gradient in this picture appears while waiting for the first picture of the remote desktop to be received (or around the said picture if the window is too large.)

SuperVNC

The user interface contains some other niceties. For instance, the toolbar icons turn black, as on Mac OS X, when pressed, pausing a remote connection dims the picture and tabs show cool mouse over/pressed effects.

If you are curious on how I implemented the mockup, I’d be more than happy to share the code with you. The tabs are drawn with a tabbed pane UI delegate, as is the slider. Anyhow, I though it’d be to show that a Swing UI does not have to look like Swing, and that can be achieved very easily.

57 Responses to “A Swing User Interface”

  1. Nice work! I’m very very curious about this application! Could you send it to me? :-P

    Cheers,
    Daniel.

  2. Congrats on finishing the studies :)

  3. Congratulations on finishing the studies. ;-) I’m also very interested on how you made your custom UI.
    Looking forward to seeing your book as well!

  4. Blaise says:

    Congratulations for your M.S.

    Me too, i’m interested by your app., can you give the code to us ?

  5. javaguy44 says:

    Definitely interested in seeing the code + app….a link to the source et al?

  6. rah003 says:

    Congrats on your M.S.
    Seems like this is your year of finishing things – Book’s done, studies’ done … care to share what’s next on the list? ;)

  7. Xavier Hanin says:

    At last, studies finished! You were going to upset me with so many achievements and still a student :-)

  8. lecuret cedric says:

    I’m very interesting in your work. Please send me code example to complete my collections about your work.
    thank you for this exelent blog

  9. herve says:

    Félicitations Romain pour en avoir finit avec l’école !! ;o)

    When do you start your internship at google ? I hope you will keep us informed about the things you’re doing there.

    @+

    hervé

  10. Romain Guy says:

    Thanks everyone. I’ll post the source code as soon as possible. I will probably release only the code of the UI since the rest is very buggy and difficult to launch.

    herve: I start on April 3! I’d love to keep you guys informed about my work there but I don’t even know myself what I’m gonna do there. I’m afraid I won’t be able to communicate about it :(

  11. Blaise says:

    Thank you for shar your source code.

    But if you need, i’m specially interested with the “remote desktop” oriented code (buggy or not)…

  12. Reading the first paragraph I got very exited – could there finally be an implementation of the unified toolbar look in pure Java? That would have been extremely nice. But oh, only a mockup, what a pity… Maybe better to wait until Leopard anyway ;) The final outcome still looks very nice though, as aways.

    Congrats for finishing your studies and best wishes for the future.

    Cheers,

  13. Jeff Dinkins says:

    \o/ Congrats Romain!!! So, when are you coming back to work with us on Swing. ;-)

  14. Bug says:

    Great work, looking forward to the source! Of course many congratulations.

  15. aron-smith says:

    Its this kind of stuff that spurs innovation, sure post your code.

    Also do you have a good link to a ‘unified toolbar’, cant seem to find something decent to explain it to me.

    I suppose it is a Mac thing, personally I am for Linux – she is cuter…

    http://blog.wired.com/cultofmac/2007/03/novell_launches.html

  16. Cesar says:

    Romain, I’ve become a real fan of the work you do on GUIs and I’m definitely buying your book. Good luck at the big G.

    P.S. still waiting for the code for this UI! :) j/k.

  17. Hi,

    this looks very nice. I wrote a similar TabbedPaneUI for our CRM-Application. Maybe you want to check it out. It even shows nice rollovers. Improvements are always welcome.

    Regards,

    Alex

    Here is the source:

    package de.webjazz.ui;

    import javax.swing.*;
    import javax.swing.plaf.ComponentUI;
    import javax.swing.plaf.basic.BasicTabbedPaneUI;
    import java.awt.*;
    import java.awt.event.MouseEvent;
    import java.awt.event.MouseListener;
    import java.awt.event.MouseMotionListener;

    public class AquaBarTabbedPaneUI extends BasicTabbedPaneUI {

    private static final Insets NO_INSETS = new Insets(0, 0, 0, 0);
    private ColorSet selectedColorSet;
    private ColorSet defaultColorSet;
    private ColorSet hoverColorSet;
    private boolean contentTopBorderDrawn = true;
    private Color lineColor = new Color(158, 158, 158);
    private Color dividerColor = new Color(200, 200, 200);
    private Insets contentInsets = new Insets(10,10,10,10);
    private int lastRollOverTab = -1;

    public static ComponentUI createUI(JComponent c) {
    return new AquaBarTabbedPaneUI();
    }

    public AquaBarTabbedPaneUI() {

    selectedColorSet = new ColorSet();
    selectedColorSet.topGradColor1 = new Color(233, 237, 248);
    selectedColorSet.topGradColor2 = new Color(158, 199, 240);

    selectedColorSet.bottomGradColor1 = new Color(112, 173, 239);
    selectedColorSet.bottomGradColor2 = new Color(183, 244, 253);

    defaultColorSet = new ColorSet();
    defaultColorSet.topGradColor1 = new Color(253, 253, 253);
    defaultColorSet.topGradColor2 = new Color(237, 237, 237);

    defaultColorSet.bottomGradColor1 = new Color(222, 222, 222);
    defaultColorSet.bottomGradColor2 = new Color(255, 255, 255);

    hoverColorSet = new ColorSet();
    hoverColorSet.topGradColor1 = new Color(244, 244, 244);
    hoverColorSet.topGradColor2 = new Color(223, 223, 223);

    hoverColorSet.bottomGradColor1 = new Color(211, 211, 211);
    hoverColorSet.bottomGradColor2 = new Color(235, 235, 235);

    maxTabHeight = 20;

    setContentInsets(0);
    }

    public void setContentTopBorderDrawn(boolean b) {
    contentTopBorderDrawn = b;
    }

    public void setContentInsets(Insets i) {
    contentInsets = i;
    }

    public void setContentInsets(int i) {
    contentInsets = new Insets(i, i, i, i);
    }

    public int getTabRunCount(JTabbedPane pane) {
    return 1;
    }

    protected void installDefaults() {
    super.installDefaults();

    RollOverListener l = new RollOverListener();
    tabPane.addMouseListener(l);
    tabPane.addMouseMotionListener(l);

    tabAreaInsets = NO_INSETS;
    tabInsets = new Insets(0,0,0,1);
    }

    protected boolean scrollableTabLayoutEnabled() {
    return false;
    }

    protected Insets getContentBorderInsets(int tabPlacement) {
    return contentInsets;
    }

    protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
    return 21;
    }

    protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
    int w = super.calculateTabWidth(tabPlacement, tabIndex, metrics);
    w += metrics.charWidth(‘M’) * 2;
    return w;
    }

    protected int calculateMaxTabHeight(int tabPlacement) {
    return 21;
    }

    protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
    Graphics2D g2d = (Graphics2D) g;
    g2d.setPaint(new GradientPaint(0, 0, defaultColorSet.topGradColor1, 0, 10, defaultColorSet.topGradColor2));
    g2d.fillRect(0, 0, tabPane.getWidth(), 10);

    g2d.setPaint(new GradientPaint(0, 10, defaultColorSet.bottomGradColor1, 0, 21, defaultColorSet.bottomGradColor2));
    g2d.fillRect(0, 10, tabPane.getWidth(), 11);
    super.paintTabArea(g, tabPlacement, selectedIndex);

    if (contentTopBorderDrawn) {
    g2d.setColor(lineColor);
    g2d.drawLine(0, 20, tabPane.getWidth() – 1, 20);
    }
    }

    protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
    Graphics2D g2d = (Graphics2D) g;
    ColorSet colorSet;

    Rectangle rect = rects[tabIndex];

    if (isSelected) {
    colorSet = selectedColorSet;
    } else if(getRolloverTab() == tabIndex) {
    colorSet = hoverColorSet;
    } else {
    colorSet = defaultColorSet;
    }

    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

    int width = rect.width;
    int xpos = rect.x;
    if(tabIndex > 0){
    width–;
    xpos++;
    }

    g2d.setPaint(new GradientPaint(xpos, 0, colorSet.topGradColor1, xpos, 10, colorSet.topGradColor2));
    g2d.fillRect(xpos, 0, width, 10);

    g2d.setPaint(new GradientPaint(0, 10, colorSet.bottomGradColor1, 0, 21, colorSet.bottomGradColor2));
    g2d.fillRect(xpos, 10, width, 11);

    if (contentTopBorderDrawn) {
    g2d.setColor(lineColor);
    g2d.drawLine(rect.x, 20, rect.x + rect.width – 1, 20);
    }
    }

    protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
    Rectangle rect = getTabBounds(tabIndex, new Rectangle(x, y, w, h));
    g.setColor(dividerColor);
    g.drawLine(rect.x + rect.width, 0, rect.x + rect.width, 20);
    }

    protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {

    }

    protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    // Do nothing
    }

    protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    // Do nothing
    }

    protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
    // Do nothing
    }

    protected void paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected) {
    // Do nothing
    }

    protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
    return 0;
    }

    private class ColorSet {
    Color topGradColor1;
    Color topGradColor2;

    Color bottomGradColor1;
    Color bottomGradColor2;
    }

    private class RollOverListener implements MouseMotionListener, MouseListener {

    public void mouseDragged(MouseEvent e) {
    }

    public void mouseMoved(MouseEvent e) {
    checkRollOver();
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    checkRollOver();
    }

    public void mouseExited(MouseEvent e) {
    tabPane.repaint();
    }

    private void checkRollOver() {
    int currentRollOver = getRolloverTab();
    if (currentRollOver != lastRollOverTab) {
    lastRollOverTab = currentRollOver;
    Rectangle tabsRect = new Rectangle(0, 0, tabPane.getWidth(), 20);
    tabPane.repaint(tabsRect);
    }
    }
    }
    }

  18. Jon Lipsky says:

    Hi Romain,

    I’ve been working on a way to get the unified tool look as in the mockup, as I wrote a similar looking application on Windows and when I moved it Mac OS X, I didn’t like the look at all. I should have it ready (time permitting) next week. I can share it with you if you want. :-)

    Jon…

  19. Romain Guy says:

    Jon: Sure, I’d love to see that. How did you proceed? Using native code, or using an undecorated JFrame?

  20. Congratulations, Romain!

  21. Jon Lipsky says:

    I used an JFrame with a custom JRootPaneUI. It’s still not perfect as there are some issues with flickering during resize and a lack of a drop shadow when used on a Mac, but it’s a start.

  22. Viswanath says:

    Romain,

    Congrats,

    vissu

  23. oliv says:

    bonjour,

    je voudrais réaliser une interface pour un programme associatif. et j’aimerais vous poser une ou 2 questions .

    est il possible de réaliser une application graphique java avec swinglabs ou autre sans qu’apparaisse les bords liés à l’OS (agrandissement,fermeture et le cadre) un peu comme un splash screen ?

    quel logiciel de développement d’interface graphique en java conseiller vous pour exploiter swinglabs et toutes fonctionnalité de celui-ci ?

    Merci d’avance

  24. Davide Raccagni says:

    Well done Romain !

  25. pollux says:

    Congratulats Romain!

    Thanks for your really nice swing works!

  26. boxlight says:

    I really like the toolbar — can you post the source for the toolbar portion of the code? Also, where did those icons come from — are they free?

    Thanks!

    boxlight

  27. Mike says:

    Congrats on the degree. Now, go get em, Tiger.

  28. Jimbo says:

    I would definetely be happy to see your code. I am currently looking for doing exactly that kind of look in an swing application. Could you send me your code or post it somewhere? Many thanks and congrats for your studies

  29. Dave Staelens says:

    Romain,
    Congratulations Romain! Can’t wait to see what you have next at JavaOne.

    Dave

  30. cobranet says:

    Well done!!!
    keep going!!

  31. Farid says:

    Bravo Romain !
    Bon courage chez Google.

    The thing with this Internet is that people will always be in touch and we sure will continue to follow your work.

    @Oliv
    Essaie JFrame.setUndecorated(true)
    Globalement lis le chapitre sur les JFrame et leurs décorations

  32. dhilshuk says:

    Hi Romain,
    First of all Congrates.
    >Hope you will back to regular R&D kind of stuff in Visual effects.
    >Ihave one Question for you.
    >Hope you might have seen the JBookPanel in Java Desktop Community at the following link.
    http://jroller.com/page/pieterjan?entry=jbookpanel_and_the_page_turn

    >Can’t apply the page turn effect to our JComponents.
    >It will much more beautiful to see if this kind of effect is applied to JPanels on back and next buttons clicks.
    >To be more detailed :
    >let us have 3 panels each panel contains back and next buttons .
    >If I am on panel 1 and click next buttonPanel2 should be visible during each back and next buttons click I want to see Page turn kind of effect applied panels.
    >I want to implement this with my JComponents .
    >Can You suggest me how to proceed.
    >Wish you could put your hands on it.
    cheers,
    Dhilshuk Reddy.

  33. Tor Norbye says:

    Congratulations on your graduation, Romain! Now come back to Sun :)

  34. Mike Johnson says:

    It may not look like a Swing GUI, but it doesn’t look like an OS X GUI either: the tabs are wrong.

    If you were using native widgets, you wouldn’t even have to speculate about this. With Swing, this is always a guessing game.

  35. @Mike: I think it never was the intention to recreate the OS X tabs. Hell, even “native” OS X apps use custom components more and more. Some of the work Romain is doing actually makes Swing a bit more bearable and easier to look at. ;-)

  36. Bruce Boughton says:

    Romain, the bit I’m most interested in understanding is the scaling (via the slider). I know you can use g2d.scale(factor) on a Graphics2D object, but since Swing using pixels as it’s units, this seems to mess up mouse event co-ordinates, etc.

    Is there a hack-free way to achive scaling of JComponents, including JContainers with children?

    Cheers,

    Bruce

  37. Bruce Boughton says:

    PS: It’d be nice if you exposed the comments RSS feed on the site (I had to find it in the blog RSS).

  38. Romain Guy says:

    Thanks everyone!

    @Mike: The tabs are not wrong. I duplicated the tabs from the application called Transmit. This kind of tab is also used in many other places/applications (like Apple’s iWork palettes.)