I figured it out. I extended JSlider to keep track of the progress separately from the current thumb position, and then overrode the MetalSliderUI's paintTrack method to draw fill the track to the position I want.
Here's the solution, stripped down the important parts.
The new progress bar:
public class ProgressSlider extends JSlider {
protected int progress;
private static final String uiClassID = "ProgressSliderUI";
public ProgressSlider() {
progress = 0;
putClientProperty("JSlider.isFilled", Boolean.TRUE);
updateUI();
}
public void updateUI() {
setUI(new ProgressSliderUI(this));
}
public String getUIClassID() {
return uiClassID;
}
public int getProgress() {
return this.progress;
}
public void setProgress(int value) {
if (value < this.getMinimum()) {
this.progress = this.getMinimum();
}
else if (value > this.getMaximum()) {
this.progress = this.getMaximum();
}
else {
this.progress = value;
}
}
}
The new UI:
Note, there were only 2 lines added to the paintTrack() method in the UI class, immediately following the comment saying such.
public class ProgressSliderUI extends MetalSliderUI {
public ProgressSliderUI() {
super();
}
public ProgressSliderUI(JSlider b) {
}
@Override
public void paintTrack(Graphics g) {
Color trackColor = !slider.isEnabled() ? MetalLookAndFeel
.getControlShadow() : Color.blue;
boolean leftToRight = true;
g.translate(trackRect.x, trackRect.y);
int trackLeft = 0;
int trackTop = 0;
int trackRight = 0;
int trackBottom = 0;
// Draw the track
if (slider.getOrientation() == JSlider.HORIZONTAL) {
trackBottom = (trackRect.height - 1) - getThumbOverhang();
trackTop = trackBottom - (getTrackWidth() - 1);
trackRight = trackRect.width - 1;
}
else {
if (leftToRight) {
trackLeft = (trackRect.width - getThumbOverhang())
- getTrackWidth();
trackRight = (trackRect.width - getThumbOverhang()) - 1;
}
else {
trackLeft = getThumbOverhang();
trackRight = getThumbOverhang() + getTrackWidth() - 1;
}
trackBottom = trackRect.height - 1;
}
if (slider.isEnabled()) {
g.setColor(MetalLookAndFeel.getControlDarkShadow());
g.drawRect(trackLeft, trackTop, (trackRight - trackLeft) - 1,
(trackBottom - trackTop) - 1);
g.setColor(MetalLookAndFeel.getControlHighlight());
g.drawLine(trackLeft + 1, trackBottom, trackRight, trackBottom);
g.drawLine(trackRight, trackTop + 1, trackRight, trackBottom);
g.setColor(MetalLookAndFeel.getControlShadow());
g.drawLine(trackLeft + 1, trackTop + 1,
trackRight - 2, trackTop + 1);
g.drawLine(trackLeft + 1, trackTop + 1, trackLeft + 1,
trackBottom - 2);
}
else {
g.setColor(MetalLookAndFeel.getControlShadow());
g.drawRect(trackLeft, trackTop, (trackRight - trackLeft) - 1,
(trackBottom - trackTop) - 1);
}
// Draw the fill
if (filledSlider) {
int middleOfThumb = 0;
int fillTop = 0;
int fillLeft = 0;
int fillBottom = 0;
int fillRight = 0;
if (slider.getOrientation() == JSlider.HORIZONTAL) {
middleOfThumb = thumbRect.x + (thumbRect.width / 2);
middleOfThumb -= trackRect.x; // To compensate for the
// g.translate()
fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
fillBottom = !slider.isEnabled() ? trackBottom - 1
: trackBottom - 2;
if (!drawInverted()) {
fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
// THIS IS THE CHANGE OF NOTE:
// Fills the progress with the value from the custom slider
fillRight = xPositionForValue(((ProgressSlider) slider)
.getProgress());
fillRight -= trackRect.x;
}
else {
fillLeft = middleOfThumb;
fillRight = !slider.isEnabled() ? trackRight - 1
: trackRight - 2;
}
}
else {
middleOfThumb = thumbRect.y + (thumbRect.height / 2);
middleOfThumb -= trackRect.y; // To compensate for the
// g.translate()
fillLeft = !slider.isEnabled() ? trackLeft : trackLeft + 1;
fillRight = !slider.isEnabled() ? trackRight - 1
: trackRight - 2;
if (!drawInverted()) {
fillTop = middleOfThumb;
fillBottom = !slider.isEnabled() ? trackBottom - 1
: trackBottom - 2;
}
else {
fillTop = !slider.isEnabled() ? trackTop : trackTop + 1;
fillBottom = middleOfThumb;
}
}
if (slider.isEnabled()) {
g.setColor(slider.getBackground());
g.drawLine(fillLeft, fillTop, fillRight, fillTop);
g.drawLine(fillLeft, fillTop, fillLeft, fillBottom);
g.setColor(trackColor);
g.fillRect(fillLeft + 1, fillTop + 1, fillRight
- fillLeft, fillBottom - fillTop);
}
else {
g.setColor(MetalLookAndFeel.getControlShadow());
g.fillRect(fillLeft, fillTop, fillRight - fillLeft,
trackBottom - trackTop);
}
}
g.translate(-trackRect.x, -trackRect.y);
}
}
And for the driver to test it:
public class ProgressExample extends JFrame
{
public ProgressExample()
{
super("Progress Example");
ProgressSlider mSlider = new ProgressSlider();
mSlider.setMinimum(0);
mSlider.setMaximum(100);
mSlider.setValue(20);
mSlider.setProgress(50);
getContentPane().setLayout(new FlowLayout());
getContentPane().add(slider);
getContentPane().add(mSlider);
}
public static void main(String args[])
{
ProgressExample f = new ProgressExample();
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e)
{
System.exit(0);
}
});
f.setSize(300, 80);
f.show();
}
}