3

I'm programming a Java app using Nimbus look & feel. Unfortunately, the appearance of indeterminate JProgressBars of Nimbus look & feel is AWFUL (see below) :

enter image description here

On the other hand, I've noticed Netbeans with Nimbus look & feel has a different indeterminate JProgressBar style which looks much better (see below) :

enter image description here

How can I use this style in my own application?

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
Seyed Mohammad
  • 798
  • 10
  • 29

2 Answers2

3

You can write your own Painter<JComponent>:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.plaf.nimbus.*;

public final class IndeterminateStyleTest {
  private final BoundedRangeModel model = new DefaultBoundedRangeModel();
  public JComponent makeUI() {
    JProgressBar progressBar0 = new JProgressBar(model);

    UIDefaults d = new UIDefaults();
    d.put("ProgressBar[Enabled+Indeterminate].foregroundPainter", new IndeterminateRegionPainter());

    JProgressBar progressBar1 = new JProgressBar(model);
    progressBar1.putClientProperty("Nimbus.Overrides", d);

    progressBar0.setIndeterminate(true);
    progressBar1.setIndeterminate(true);

    JPanel p = new JPanel();
    p.setBorder(BorderFactory.createEmptyBorder(32, 5, 32, 5));
    p.add(progressBar0);
    p.add(progressBar1);
    return p;
  }
  public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
    });
  }
  public static void createAndShowGUI() {
    try {
      for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) {
        if ("Nimbus".equals(laf.getName())) {
          UIManager.setLookAndFeel(laf.getClassName());
        }
      }
    } catch (ClassNotFoundException | InstantiationException |
               IllegalAccessException | UnsupportedLookAndFeelException ex) {
      ex.printStackTrace();
    }
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new IndeterminateStyleTest().makeUI());
    f.setSize(320, 240);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class IndeterminateRegionPainter extends AbstractRegionPainter {
  // Copied from javax.swing.plaf.nimbus.ProgressBarPainter.java
  private Color color17 = decodeColor("nimbusOrange",  .0f,           .0f,         .0f,       -156);
  private Color color18 = decodeColor("nimbusOrange", -.015796512f,   .02094239f, -.15294117f,   0);
  private Color color19 = decodeColor("nimbusOrange", -.004321605f,   .02094239f, -.0745098f,    0);
  private Color color20 = decodeColor("nimbusOrange", -.008021399f,   .02094239f, -.10196078f,   0);
  private Color color21 = decodeColor("nimbusOrange", -.011706904f,  -.1790576f,  -.02352941f,   0);
  private Color color22 = decodeColor("nimbusOrange", -.048691254f,   .02094239f, -.3019608f,    0);
  private Color color23 = decodeColor("nimbusOrange",  .003940329f,  -.7375322f,   .17647058f,   0);
  private Color color24 = decodeColor("nimbusOrange",  .005506739f,  -.46764207f,  .109803915f,  0);
  private Color color25 = decodeColor("nimbusOrange",  .0042127445f, -.18595415f,  .04705882f,   0);
  private Color color26 = decodeColor("nimbusOrange",  .0047626942f,  .02094239f,  .0039215684f, 0);
  private Color color27 = decodeColor("nimbusOrange",  .0047626942f, -.15147138f,  .1607843f,    0);
  private Color color28 = decodeColor("nimbusOrange",  .010665476f,  -.27317524f,  .25098038f,   0);
  private Rectangle2D rect = new Rectangle2D.Float(0, 0, 0, 0);
  private Path2D path = new Path2D.Float();
  private PaintContext ctx = new PaintContext(new Insets(5, 5, 5, 5), new Dimension(29, 19), false);
  @Override public void doPaint(Graphics2D g, JComponent c, int width, int height, Object[] extendedCacheKeys) {
    path = decodePath1();
    g.setPaint(color17);
    g.fill(path);
    rect = decodeRect3();
    g.setPaint(decodeGradient5(rect));
    g.fill(rect);
    rect = decodeRect4();
    g.setPaint(decodeGradient6(rect));
    g.fill(rect);
  }
  @Override public PaintContext getPaintContext() {
    return ctx;
  }
  private Path2D decodePath1() {
    path.reset();
    path.moveTo(decodeX(0.6f), decodeY(0.12666667f));
    path.curveTo(decodeAnchorX(0.6000000238418579f, -2.0f), decodeAnchorY(0.12666666507720947f, 0.0f), decodeAnchorX(0.12666666507720947f, 0.0f), decodeAnchorY(0.6000000238418579f, -2.0f), decodeX(0.12666667f), decodeY(0.6f));
    path.curveTo(decodeAnchorX(0.12666666507720947f, 0.0f), decodeAnchorY(0.6000000238418579f, 2.0f), decodeAnchorX(0.12666666507720947f, 0.0f), decodeAnchorY(2.4000000953674316f, -2.0f), decodeX(0.12666667f), decodeY(2.4f));
    path.curveTo(decodeAnchorX(0.12666666507720947f, 0.0f), decodeAnchorY(2.4000000953674316f, 2.0f), decodeAnchorX(0.6000000238418579f, -2.0f), decodeAnchorY(2.8933334350585938f, 0.0f), decodeX(0.6f), decodeY(2.8933334f));
    path.curveTo(decodeAnchorX(0.6000000238418579f, 2.0f), decodeAnchorY(2.8933334350585938f, 0.0f), decodeAnchorX(3.0f, 0.0f), decodeAnchorY(2.8933334350585938f, 0.0f), decodeX(3.0f), decodeY(2.8933334f));
    path.lineTo(decodeX(3.0f), decodeY(2.6f));
    path.lineTo(decodeX(0.4f), decodeY(2.6f));
    path.lineTo(decodeX(0.4f), decodeY(0.4f));
    path.lineTo(decodeX(3.0f), decodeY(0.4f));
    path.lineTo(decodeX(3.0f), decodeY(0.120000005f));
    path.curveTo(decodeAnchorX(3.0f, 0.0f), decodeAnchorY(0.12000000476837158f, 0.0f), decodeAnchorX(0.6000000238418579f, 2.0f), decodeAnchorY(0.12666666507720947f, 0.0f), decodeX(0.6f), decodeY(0.12666667f));
    path.closePath();
    return path;
  }
  private Rectangle2D decodeRect3() {
    rect.setRect(decodeX(0.4f), //x
                 decodeY(0.4f), //y
                 decodeX(3.0f) - decodeX(0.4f), //width
                 decodeY(2.6f) - decodeY(0.4f)); //height
    return rect;
  }
  private Rectangle2D decodeRect4() {
    rect.setRect(decodeX(0.6f), //x
                 decodeY(0.6f), //y
                 decodeX(2.8f) - decodeX(0.6f), //width
                 decodeY(2.4f) - decodeY(0.6f)); //height
    return rect;
  }
  private Paint decodeGradient5(Shape s) {
    Rectangle2D bounds = s.getBounds2D();
    float x = (float)bounds.getX();
    float y = (float)bounds.getY();
    float w = (float)bounds.getWidth();
    float h = (float)bounds.getHeight();
    return decodeGradient((0.5f * w) + x, (0.0f * h) + y, (0.5f * w) + x, (1.0f * h) + y,
                          new float[] { 0.038709678f, 0.05483871f, 0.07096774f, 0.28064516f, 0.4903226f, 0.6967742f, 0.9032258f, 0.9241935f, 0.9451613f },
                          new Color[] { color18,
                                        decodeColor(color18, color19, 0.5f),
                                        color19,
                                        decodeColor(color19, color20, 0.5f),
                                        color20,
                                        decodeColor(color20, color21, 0.5f),
                                        color21,
                                        decodeColor(color21, color22, 0.5f),
                                        color22
                                      });
  }

  private Paint decodeGradient6(Shape s) {
    Rectangle2D bounds = s.getBounds2D();
    float x = (float)bounds.getX();
    float y = (float)bounds.getY();
    float w = (float)bounds.getWidth();
    float h = (float)bounds.getHeight();
    return decodeGradient((0.5f * w) + x, (0.0f * h) + y, (0.5f * w) + x, (1.0f * h) + y,
                          new float[] { 0.038709678f, 0.061290324f, 0.08387097f, 0.27258065f, 0.46129033f, 0.4903226f, 0.5193548f, 0.71774197f, 0.91612905f, 0.92419356f, 0.93225807f },
                          new Color[] { color23,
                                        decodeColor(color23, color24, 0.5f),
                                        color24,
                                        decodeColor(color24, color25, 0.5f),
                                        color25,
                                        decodeColor(color25, color26, 0.5f),
                                        color26,
                                        decodeColor(color26, color27, 0.5f),
                                        color27,
                                        decodeColor(color27, color28, 0.5f),
                                        color28
                                      });
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
0

You may also subclass from JProgressBar and override paintComponent method. This gives you complete freedom in drawing whatever you like. And even maybe less code than accepted solution.

Here's an example of overriding only the indeterminate behavior of Nimbus with more windows-like gradient look: Windows-like gradient look for Nimbus JProgressBar indeterminate

The disadvantage of this solution is that you need to explicitly dispose() the component (or maybe I haven't found the appropriate way to shutdown executor automatically)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.UIManager;
import javax.swing.WindowConstants;

public final class Test
{
  public JComponent makeUI()
  {
    final JProgressBar progressBar=new GradientProgressBar();
    progressBar.setPreferredSize(new Dimension(700, 30));
    progressBar.setIndeterminate(true);
    progressBar.setStringPainted(true);
    progressBar.setString("Loading...");
    final JPanel p=new JPanel();
    p.setBorder(BorderFactory.createEmptyBorder(32, 5, 32, 5));
    p.add(progressBar);
    return p;
  }

  public static void main(String[] args)
  {
    EventQueue.invokeLater(()->createAndShowGUI());
  }

  public static void createAndShowGUI()
  {
    try
    {
      for (UIManager.LookAndFeelInfo laf:UIManager.getInstalledLookAndFeels())
      {
        if ("Nimbus".equals(laf.getName()))
        {
          UIManager.setLookAndFeel(laf.getClassName());
        }
      }
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    JFrame f=new JFrame();
    f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    f.getContentPane().add(new Test().makeUI());
    f.setSize(800, 600);
    f.setLocationRelativeTo(null);
    f.setVisible(true);
  }
}

class GradientProgressBar extends JProgressBar
{
  private static final long serialVersionUID=8886375125424236114L;
  private static final int UPDATE_INTERVAL_MS=50;
  private static final AtomicInteger THREAD_NUM=new AtomicInteger();
  private static final AtomicReference<ScheduledThreadPoolExecutor> executor=new AtomicReference<>();
  private static final Font FONT=new Font("SansSerif", Font.BOLD, 12);
  private static final int R_BACK=214;
  private static final int G_BACK=217;
  private static final int B_BACK=223;
  private static final int COLOR_DELTA=20;
  private static final Color COLOR_BAR=new Color(191, 98, 4);

  private final AtomicInteger position=new AtomicInteger(-20);

  @Override
  protected void paintComponent(final Graphics g)
  {
    if (isIndeterminate())
    {
      drawGradient((Graphics2D) g);
      adjustPosition();
    }
    else
    {
      super.paintComponent(g);
    }
  }

  private void drawGradient(final Graphics2D g)
  {
    if (getWidth() > 0 && getHeight() > 0)
    {
      final int gradWidth=getWidth() / 4;
      final float posF=(float) position.get();
      for (int y=0;y < getWidth();y++)
      {
        GradientPaint grad=new GradientPaint(posF, 0.0f, getBackgroundAtY(y), posF + (float) gradWidth / 2.0f, 0.0f, COLOR_BAR);
        Rectangle rect=new Rectangle(0, y, position.get() + gradWidth / 2, 1);
        g.setPaint(grad);
        g.fill(rect);
        grad=new GradientPaint(posF + (float) gradWidth / 2.0f, 0.0f, COLOR_BAR, posF + (float) gradWidth, 0.0f, getBackgroundAtY(y));
        rect=new Rectangle(position.get() + gradWidth / 2, y, getWidth() - position.get(), 1);
        g.setPaint(grad);
        g.fill(rect);
      }
      drawTitle(g);
    }
  }

  public void drawTitle(final Graphics g)
  {
    if (isStringPainted() && getString() != null && !getString().isBlank())
    {
      g.setColor(Color.BLACK);
      final FontMetrics metrics=g.getFontMetrics(FONT);
      final int x=(getWidth() - metrics.stringWidth(getString())) / 2;
      final int y=(getHeight() - metrics.getHeight()) / 2 + metrics.getAscent();
      g.setFont(FONT);
      g.drawString(getString(), x, y);
    }
  }

  private Color getBackgroundAtY(final int y)
  {
    if (y <= 2 || y >= getHeight() - 2)
    {
      // Border
      return new Color(R_BACK - COLOR_DELTA, G_BACK - COLOR_DELTA, B_BACK - COLOR_DELTA);
    }
    // Vertical gradient
    final int height=(getHeight() - 4) / 2;
    final int delta=Math.abs(height - y);
    final int colorDelta=COLOR_DELTA * delta / height;
    return new Color(R_BACK + colorDelta, G_BACK + colorDelta, B_BACK + colorDelta);
  }

  @Override
  public void setIndeterminate(boolean newValue)
  {
    if (newValue != isIndeterminate())
    {
      super.setIndeterminate(newValue);
      if (newValue)
      {
        executor.set(new ScheduledThreadPoolExecutor(1, r -> new Thread(r, "guihelper_progress-" + THREAD_NUM.incrementAndGet())));
        executor.get().scheduleAtFixedRate(() -> repaint(), 0, UPDATE_INTERVAL_MS, TimeUnit.MILLISECONDS);
      }
      else
      {
        if (executor.get() != null)
        {
          executor.get().shutdownNow();
        }
      }
    }
  }

  public void dispose()
  {
    if (executor.get() != null)
    {
      executor.get().shutdownNow();
    }
  }

  private void adjustPosition()
  {
    int step=getWidth() * UPDATE_INTERVAL_MS / 10000;
    if (step <= 0)
    {
      step=1;
    }
    position.set(position.get() + step);
    if (position.get() > getWidth())
    {
      position.set(-getWidth() / 4);
    }
  }
}
TT.
  • 15,774
  • 6
  • 47
  • 88