Making A Slider Component Problem

I’m creating a slider component for my game with graphics in Slick2D. Almost everything is working as supposed but there’s a problem, to properly control the slider box. As you can see below in the code, I’m increasing or decreasing the value, instead of changing the sliderBoxX by the position of the mouseX. So tell me should I start all over again or is it still possible to make this code working in this code order, to make sliderBoxX and component values change by mouse X coordinate?
As a matter of a problem, is the order of the code where I compute and check things wrong?

Code is inside update method:


this.minusBoxX = this.x;
this.minusBoxY = this.y;
this.minusBoxWidth = this.height;
this.minusBoxHeight = this.height;

this.plusBoxX = this.x + this.width - this.height;
this.plusBoxY = this.y;
this.plusBoxWidth = this.height;
this.plusBoxHeight = this.height;
        
this.sliderBoxWidth = this.height / 2;
this.sliderBoxHeight = this.height / 2;

this.minSliderX = this.minusBoxX + this.minusBoxWidth;
this.maxSliderX = this.plusBoxX;
this.lineWidth = Math.abs(minSliderX - maxSliderX) - this.sliderBoxWidth;

this.percentage = this.value / this.maxValue;

this.sliderBoxX = this.minSliderX + (this.lineWidth * this.percentage);
this.sliderBoxY = this.y + ((this.y + this.height) - this.y) / 2
                         - (this.sliderBoxHeight / 2);

float minSliderBoxX = this.sliderBoxX;
float minSliderBoxY = this.sliderBoxY;
float maxSliderBoxX = this.sliderBoxX + this.sliderBoxWidth;
float maxSliderBoxY = this.sliderBoxY + this.sliderBoxHeight;
if ((mx > minSliderBoxX && mx < maxSliderBoxX) && (my > minSliderBoxY && my < maxSliderBoxY)) {
    if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
        if (this.percentage * 100 >= 0 && this.percentage * 100 <= 100) {
            this.wasSliderBoxDown = true;
        }
    }
}
if (this.wasSliderBoxDown) {
    if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
        if (this.percentage * 100 >= 0 && this.percentage * 100 <= 100) {
            // this.sliderBoxX = mx; something like this
            if (mx > maxSliderBoxX) {
                this.value += 0.1f * 100.0f; // this works, but is not correct
            }
            if (mx < minSliderBoxX) {
                this.value -= 0.1f * 100.0f; // this works, but is not correct
            }
        }
    } else {
        this.wasSliderBoxDown = false;
    }
}


Edit:
Ok, I’m changing mind. I will make first the slider line made of a component width, and not like I did between minus and plus boxes. But now the questions is, what should I change current value or a slider box position from the mx and then compute percentage? And what checks should I provide, like should I check the percentage, value or current mx positon for bounds?

I’ve not tried anything like this before but I’d assume you would have to:

  • Calculate the distance of 1% of the slider width
  • When the slider is clicked, calculate where it is clicked
  • Calculate the X-distance that the mouse has been moved and round it down to the nearest 1%
  • Redraw the slider and update as you wish

Alright, thanks on the suggestion. I will take a deep look at it, after I back home.

I’ll have to make some assumptions about the rest of your class, so forgive me if I’m incorrect in some aspects. My first comment is that when designing the basic controls, keep them as simple and focused as possible. The plus and minus buttons shouldn’t be part of a base slider class IMHO. They can always be “attached” to a basic slider control via events further up the abstraction chain. As it is, they make the calculations for the slider more complex than they needs to be. (Technically the drag box could be separated out as well, but we’ll leave it for now since it doesn’t complicate things too much.)

Lets assume your slider control consists of 2 items, a drag box, and a line that represents the extents that the drag box can be moved. The code would be something like this:

       
/* 
 * Technically the 5 variables below shouldn't be calculated every 
 * update. They should be computed when the control is initialized 
 * and only recalculated if the control is moved/resized or if 
 * their dimensions are altered through setter methods.
 */
this.sliderBoxWidth = this.height >> 1;
this.sliderBoxHeight = this.height >> 1;
this.sliderBoxY = this.y;
this.lineWidth = this.width - this.sliderBoxWidth;
/* 
 * I'm guessing you have a variable similar to this in your class
 * even though it's not listed in your snippet.
 */
this.lineX = this.x + (this.sliderBoxWidth>>1);

// Assume that the control isn't pressed by default.
this.wasSliderBoxDown = false;

// Compute the mouse position in relation to the x position of the drag line.
int mx_relative = mx - lineX;

/*
 * Check to make sure that the mouse is being pressed and that its relative 
 * position is within our drag line bounds.
 */
if(input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON) && mx_relative >= 0 && mx_relative <= this.lineWidth) {
    // Do the value/offset computations if the mouse is in bounds.
    this.percentage = (float)(mx_relative/this.lineWidth);
    this.value = Math.round(this.percentage * 100);
    // The following will center the drag box on the mouse's position.
    this.sliderBoxX = mx_relative + this.lineX - (sliderBoxWidth>>1);
    // Indicate that the control is being pressed.
    this.wasSliderBoxDown = true;
}

You’ll have to excuse any minor errors in the above code; I didn’t have time to test it in my IDE, but I’m pretty sure the calculations should work. Hope this helps. 8)

Thank you on the constructive answer, I very appreciate it.
However, this is my first time creating GUI components, also my first serious game project, using some library.
By the way, it’s nice to see that you’re using shift operators, for which I’ve never had a chance to use them before nor to see them in any other code. So, I’d be thankful if you could tell me what this.height >> 1 does? Does it checks if the current height value was changed for 1 ?
Anyway, I’m really tired now, and I have to wake up early in a morning, so I will probably continue programming tommorow tonight and try out your solution.
I will reply if it worked.
All best!

Ah, sorry about that. I’m so used to using bit shifts that I sometimes forget not everybody is familiar with them. the “>>1” essentially divides a number in half and discards the remainder. It all comes down to the way binary representations of numbers work. If you’re unfamiliar with binary operations, I’d suggest doing some research into them sometime. It’s definitely not a “drop everything and learn it now” type of deal, but the concepts can be quite useful and you’ll find them in most languages. :slight_smile:

Slick2D has it’s own way of doing certain tasks, but it’s not all that different from using vanilla Java in most aspects. I’m pretty much doing what you’re doing at the moment and building a set of controls for use in my engine. It seems a bit overwhelming at first, but once you get into it and start breaking down how things work, it’s not too bad for the most part. Best of luck, and keep us updated on your progress. 8)

Hello again! It’s been a week since last time we talk, I said I will continue tommorow night, but some other plans came out…
So you say that shift operator “>>”, divides a number in half and discards the remainder, which I do understand now,
but, you also say in your comment above this code,


/* 
 * Technically the 5 variables below shouldn't be calculated every 
 * update. They should be computed when the control is initialized 
 * and only recalculated if the control is moved/resized or if 
 * their dimensions are altered through setter methods.
 */
this.sliderBoxWidth = this.height >> 1;
this.sliderBoxHeight = this.height >> 1; 
this.sliderBoxY = this.y;
this.lineWidth = this.width - this.sliderBoxWidth;

that this variables should be computed when the control is initialized and only recalculated if the control is moved/resized.
S, how this shift operator checks as well if a value is different (moved/resized), as you did not use an if condition,
or it was just your suggestion to me, in order to tell me how things should be done?

Anyway, let’s back to the topic.
Everything works as intended, but the only problem now is I’m not getting a proper percentage and a value.

If i write,


this.percentage = (mxRelative / this.lineWidth) * 100.0f;
this.value = this.percentage / 100.0f * this.maxValue;

and I go maximum with slider box, this will return:
percentage: 99.775276
value: 997.7528
Which is not a 100%, and not 1000.00

I can round percentage, and it will go to 100 for percentage, and 1000.0 for value

this.percentage = Math.round((mxRelative / this.lineWidth) * 100.0f);
this.value = this.percentage / 100.0f * this.maxValue;

but that’s not what I need, I need a precise number, like 957.50

I guess I could have been a bit more clear. :slight_smile: I meant that ideally, those calculations should be moved outside of the update method and into their own “calculate dimensions” method so they weren’t called each time the slider was updated. Instead you would call the “calculate dimensions” function from any methods that changed the dimensions of the control, for example any set size/dimension method, or anything method which set paddings or border widths on the control. This way the calculations are only done when needed.

For example:


public class MySliderClass {
    private int sliderBoxWidth;
    private int sliderBoxHeigt;
    private int lineWidth;
    private int borderSize;
    ...

    protected void calculateDimensions() {
        this.sliderBoxWidth = this.height >> 1;
        this.sliderBoxHeight = this.height >> 1; 
        this.sliderBoxY = this.y;
        this.lineWidth = this.width - this.sliderBoxWidth;
    }

    public void setControlSize(int w, int h) {
        ...
        calculateDimensions();
        ...
    }

    public void setBorderSize(int size) {
        ...
        calculateDimensions();
        ...
    }

    public void update() {
        // No need to do the calculations here. They're already done for 
        // us by the other methods on an as needed basis.
    }
}

After replying to your topic, I went through the exercise of implementing my own slider to make sure I hadn’t steered you in a complete wrong direction, and can say with some certainty that you should be fine leaving those calculations within the update function. :wink: It was more of a possible optimization suggestion than a must do. In the end, for simplicity sake, I left my calculations in the render function for now and it works fine for me; in all honesty I probably will go back at some point soon and refactor it to conform to my own advice. One other note, after further consideration, the sliderBoxY “calculation” should probably stay within the update method since the other calculations don’t rely on it.

Interesting. ??? If I had to take a guess, maybe you’re forgetting to account for overhang of the slider when you compute mxRelative. This would make the slider think it’s slightly to the left of where it appears to be. If you’re clamping your values, then you wouldn’t notice the discrepancy when the slider was all the way to the left since its value would be less than 0, the clamping would force it up to 0. At the rightmost position the clamping wouldn’t be in effect since you’re value is still less than 1.0f so it would be apparent there.

If you’re clamping your values, do a test with the clamping disabled and see if the minimum value returned by the slider is less than 0. If it is, then recheck your mxRelative calculation. If it’s at exactly 0.0f without clamping, then try posting the relevant sections of your updated class and maybe I can spot what’s going awry.

If it helps, here is the source to the quick slider implementation I threw together. It’s rough in spots and uncommented, but it works. I’ve highlighted the line where I’m calculating the relative mouse position to give you an idea of what I’m talking about with including the overhang. If you have any questions about it, just let me know and I’ll do my best to answer. One last note, the “computeRenderingOffset()” function returns a Point that represents the control’s absolute position on the screen. Since my control layout is based on relative position to a parent control, the controls x/y position is never used directly.

Sorry for the long winded reply. HTH. 8)

Well, this is the only component so far with which I’m stuck, and I think I know where’s the problem, but right now I just can’t throw it out of my head.
Anyway, I’m going to call now a sleep() method on self, it will help me definitely… and I believe I will find out the solution if not and more. Also, it is really intersting that you name your class HSlider, I call it PSlider, the reason is, I’m making a Poker game, just like the one on the facebook, and I’m not going to talk about how big differences are between your component design and my :D. But I still like the way I created it, even tho it only contains one AbstractComponent, it still does some job and cuts a lot of the trash code from the component in general.
Hopefully, I will learn this semester pretty well UML diagrams, so I can start some serious projects and give focus more on implementation and less coding.
Thanks again and gn!

No! Not UML… UML is banned in many workplaces, causes arguments, drives people crazy :slight_smile:

But if you want to play with quick UML diagrams:

Ah, don’t put too much thought into that one. The “H” is just for horizontal. I still haven’t decided whether I will make a “V” slider as well, or if I’m just going to add some sort of orientation logic into the mix and just end up with a Slider component. At the moment their being implemented for a map editor I’m writing so I’ve aimed more for functional than beautiful when it comes to how things are implemented. I’m sure I’ll tweak them a lot more before having them ready for final product. A poker game sounds like a fun project.

Nothing wrong with going about things a different way. For better or worse, a lot of my GUI control design is influenced by the way Swing does things; that’s what I’m most familiar/comfortable with. So long as a solution satisfactorily addresses a given problem, it’s a good one. ;D As a note, the controls I have tend to be fairly light weight. The control manager that glues them all together is a different story. LOL

I’m more of a “git er’ done” type of coder. I’ve messed around with graphing out program flow, but at the end of the day I get a better understanding of a problem through experimentation rather than theorization. Different strokes for different folks though. :wink: Have a good night, and let us know how your project progresses. 8)

[quote]No! Not UML… UML is banned in many workplaces, causes arguments, drives people crazy :slight_smile:

But if you want to play with quick UML diagrams:
[/quote]
I forgot to mention, I’m not going to learn UML for fun, that’s just my task, because it will be on my exam, unfortunately …

CodeHead, I just gave up on trying to fix my slider component with those +/- boxes on the left and right side.
So I started following your instructions, with first code, but I ended up with the same problem as with +/- box I had, so incorrect value and percentage.

This is how the slider looks now, also you can see the maximum value in text field, which shows the value(0-1000)/percentage.

p.s. I know, many things are not done properly here, for example using the listener interfaces, instead I was doing everything in actionPerform a.k.a. update method…
But I will take care of that later…


public class PSlider extends PAbstractComponent {

    private float value;
    private float percentage;
    private float minValue, maxValue;
    
    private float lineWidth, lineHeight, lineX, lineY;
    private float sliderBoxWidth, sliderBoxHeight, sliderBoxX, sliderBoxY;
    
    private boolean wasSliderBoxDown = false;
            
    private float minusBoxWidth, minusBoxHeight, minusBoxX, minusBoxY;
    private float plusBoxWidth, plusBoxHeight, plusBoxX, plusBoxY;
    
    private PLabel label;
    
    public PSlider(float width, float height) throws SlickException {
        super();
        
        this.width = width;
        this.height = height;
        
        this.label = new PLabel(PFontType.LucidiaFaxRegular, 26, false, false);
        this.label.setNormalColor(new Color(0, 0, 0, .75f));
        
        this.value = 0;
    }
    
    public PSlider(float width, float height, float currentValue) throws SlickException {
        this(width, height);
        
        this.value = currentValue;
    }

    @Override
    public void render(Graphics g, float x, float y) throws SlickException {
        this.x = x;
        this.y = y;
        
        // render rect and fill
        float cornerRadius = 0;
        g.setColor(new Color(0, 0, 0, .5f));
        g.fillRoundRect(x, y, width, height, (int) cornerRadius);
        g.setColor(Color.decode("#ffffbe"));
        g.drawRoundRect(x, y, width, height, (int) cornerRadius);
        
        // render line
        float lineX1 = this.lineX;
        float lineX2 = this.lineX + this.lineWidth;
        float lineY1 = this.lineY;
        float lineY2 = lineY1;
        g.setColor(Color.decode("#ffffbe"));
        g.drawLine(lineX1, lineY1, lineX2, lineY2);
        
        // render slider box
        float sBoxX = this.sliderBoxX;
        float sBoxY = this.sliderBoxY;
        float sBoxWidth = this.sliderBoxWidth;
        float sBoxHeight = this.sliderBoxHeight;
        g.setColor(new Color(223, 182, 105));
        g.fillRoundRect(sBoxX, sBoxY, sBoxWidth, sBoxHeight, (int) cornerRadius);
        g.setColor(Color.decode("#ffffbe"));
        g.drawRoundRect(sBoxX, sBoxY, sBoxWidth, sBoxHeight, (int) cornerRadius);
    }
    
    public void performAction(GameContainer gc, int mx, int my) throws SlickException {
        Input input = gc.getInput();

        this.sliderBoxWidth = this.height / 2;
        this.sliderBoxHeight = this.height / 2;
        this.sliderBoxY = this.y + ((this.y + this.height) - this.y) / 2
                                 - (this.sliderBoxHeight / 2);
        
        this.lineWidth = this.width - this.sliderBoxWidth;
        this.lineHeight = 0;
        this.lineX = this.x + (this.sliderBoxWidth / 2);
        this.lineY = this.y + ((this.y + this.height) - this.y) / 2;
        
        float mxRelative = mx - this.lineX;

        if ((this.sliderBoxX < this.lineX) || (this.sliderBoxX > this.lineX + this.lineWidth)) {
            this.sliderBoxX = this.lineX;
        }
        
        if ((my > this.y) && my < this.y + this.height) {
            if (input.isMousePressed(Input.MOUSE_LEFT_BUTTON)) {
                if ((mxRelative >= 0) && (mxRelative <= this.lineWidth)) {
                    this.percentage = (float) (mxRelative / this.lineWidth);
                    this.value = Math.round(this.percentage * 100);
                    this.sliderBoxX = mxRelative + this.lineX - (sliderBoxWidth / 2);
                }
            }
        }
        
        float minSliderBoxX = this.sliderBoxX;
        float minSliderBoxY = this.sliderBoxY;
        float maxSliderBoxX = this.sliderBoxX + this.sliderBoxWidth;
        float maxSliderBoxY = this.sliderBoxY + this.sliderBoxHeight;
        if ((mx > minSliderBoxX && mx < maxSliderBoxX) && (my > minSliderBoxY && my < maxSliderBoxY)) {
            if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
                this.wasSliderBoxDown = true;
            }
        }
        
        if (this.wasSliderBoxDown) {
            if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
                if (mxRelative >= 0 && mxRelative <= this.lineWidth) {
                    this.percentage = (float) (mxRelative / this.lineWidth);
                    this.value = Math.round(this.percentage * 100);
                    this.sliderBoxX = mxRelative + this.lineX - (sliderBoxWidth / 2);
                }
            } else {
                this.wasSliderBoxDown = false;
            }
        }
    }
    
    public void setInitialValues(float minValue, float maxValue) {
        if (this.minValue != minValue) {
            this.minValue = minValue;
        }
        if (this.maxValue != maxValue) {
            this.maxValue = maxValue;
        }
        
    }
    
    public float getValue() {
        return value;
    }

    public float getPercentage() {
        return percentage;
    }
}

Here, I felt bad to see you couldn’t get it working 100% the way you wanted. I made a rough implementation of a slider with this code. (It is very rough because you have to click on the sliding nub to make it move, a better implementation would be to have it effect the whole bar).

You can copy and paste it straight into a new Java file and you should be able to run it. I did this for you to get the concept of how a typical slider works. There are no external libraries required to run this…


import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;

import javax.swing.JComponent;
import javax.swing.JFrame;

public class SliderExample extends JComponent implements Runnable, MouseListener, MouseMotionListener{

	private JFrame window;
	private BufferedImage bimg;
	
	//Mouse Position data
	public int mouseX;
	public int mouseY;
	public boolean leftClick;
	public boolean rightClick;
	
	//For setting where to position the slider
	public int offsetX;
	public int offsetY;
	
	//For setting the sliders Position
	public int sliderPos;
	
	//For setting the sliders Length
	public int sliderMax;
	
	public static void main(String args[]){
		SliderExample cool = new SliderExample();
		cool.showWindow();
	}
	
	public void init(){
		offsetX = 100;
		offsetY = 100;
		
		sliderPos = 0;
		sliderMax = 150;
	}
	
	public void updateRender(Graphics2D g){
		////////////////
		//Logic Portion
		////////////////
		
		//If left mouse button is clicked and mouse is inside slider
		if(leftClick && mouseX-offsetX > sliderPos-10 && mouseX-offsetX < sliderPos+10 && 
			mouseY-offsetY > 20 && mouseY-offsetY < 40){
			
			//Move the slider to position
			sliderPos = mouseX-offsetX-5;
			
			//Keep the slider within the bounds
			if(sliderPos < 0)
				sliderPos = 0;
			if(sliderPos > sliderMax)
				sliderPos = sliderMax;
		}
		
		
		/////////////////
		//Render Portion
		/////////////////
		
		//The box surrounding the slider
		g.setColor(Color.BLACK);
		g.drawRect(offsetX-30, offsetY-20, 60+sliderMax, 40);
		
		//The slider movement bar
		g.drawRect(offsetX, offsetY-5, sliderMax, 10);
		
		//The slider itself
		g.setColor(Color.white);
		g.fillRect(offsetX+sliderPos-5, offsetY-10, 10, 20);
		g.setColor(Color.black);
		g.drawRect(offsetX+sliderPos-5, offsetY-10, 10, 20);
		
		//Percentage Number
		g.drawString("("+sliderPos+"/"+sliderMax+")", 10, 270);
		g.drawString((((double)sliderPos/sliderMax)*100)+"%", 10, 285);
	}
	
	//////////////////////////////////////////////////////////////////
	//Below is the unimportant boilerplate to make the slider visible
	//////////////////////////////////////////////////////////////////
	
	public SliderExample(){
		 window = new JFrame("Slider Example");
         window.setBackground(Color.BLACK);
         setBackground(Color.BLACK);
         init();
	}
	
	public void showWindow(){
		 Thread looper = new Thread(this);
         looper.start();
		 window.add(this, BorderLayout.CENTER);
		 window.addMouseListener(this);
		 window.addMouseMotionListener(this);
	     window.validate();
	     window.setVisible(true);
	     window.pack();
	}
	
	@Override
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        createGraphics2D((Graphics2D)g, getSize().width, getSize().height);
        //Draws a non-flickering image
        g.drawImage(bimg, 0, 0, this);
    }
	
	private void createGraphics2D(Graphics2D g2, int w, int h) {
        if (bimg == null || bimg.getWidth() != w || bimg.getHeight() != h)
            bimg = (BufferedImage) createImage(w, h);

        g2 = bimg.createGraphics();
        g2.setColor(Color.WHITE);
        g2.fillRect(0, 0, w, h);
        
        updateRender(g2);     
    }
	
	@Override
    public Dimension getPreferredSize(){
        return new Dimension(400, 300);
    }
	
	@Override
	public final void run() {
        try{           
            while(true){
                Thread.sleep(10);
                repaint();
            }
        }catch(Exception e){
            System.err.println(e.getMessage());
            System.exit(0);
        }
    }

	private void handleMouse(MouseEvent e){
		requestFocus();
        switch(e.getID()){
        	case MouseEvent.MOUSE_PRESSED:   
        		if(e.getButton() == 1)
        			leftClick = true;
        		else if(e.getButton() == 3)
        			rightClick = true;
        		break;
        	case MouseEvent.MOUSE_RELEASED:
        		if(e.getButton() == 1)
        			leftClick = false;
        		else if(e.getButton() == 3)
        			rightClick = false;
        		break;
        	case MouseEvent.MOUSE_MOVED:
        	case MouseEvent.MOUSE_DRAGGED:
        		mouseX = e.getX();
        		mouseY = e.getY();
        		break;
        }
	}

	@Override
	public void mouseDragged(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mouseExited(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mousePressed(MouseEvent e) {
		handleMouse(e);
	}

	@Override
	public void mouseReleased(MouseEvent e) {
		handleMouse(e);
	}	
}

Hopefully, you’ll be able to read through the comments to figure out how it works. If you have any questions, please ask.

Thanks for your help ctomni231. But the thing is I need to adapt the percentage on the width, so width scale should not change if I change the max value, I mean, it should not increase the scale width.

I had to do a little tweaking with your posted code to get it into a test harness, but I managed to confirm a suspicion. It’s floating point errors that are throwing the percentages off. Give the below version a try and see if you have better luck. It just adds some rounding when doing the calculations. Even though Slick accepts them, you should really use ints when possible for doing screen positioning. Let me know if this works any better. If not, I can upload the test harness version to see if that sheds any more light on the situation. 8)


public class PSlider extends PAbstractComponent {

    private float value;
    private float percentage;
    private float minValue, maxValue;
    
    private float lineWidth, lineHeight, lineX, lineY;
    private float sliderBoxWidth, sliderBoxHeight, sliderBoxX, sliderBoxY;
    
    private boolean wasSliderBoxDown = false;
            
    private float minusBoxWidth, minusBoxHeight, minusBoxX, minusBoxY;
    private float plusBoxWidth, plusBoxHeight, plusBoxX, plusBoxY;
    
    private PLabel label;
    
    public PSlider(float width, float height) throws SlickException {
        super();
        
        this.width = width;
        this.height = height;
        
        this.label = new PLabel(PFontType.LucidiaFaxRegular, 26, false, false);
        this.label.setNormalColor(new Color(0, 0, 0, .75f));
        
        this.value = 0;
    }
    
    public PSlider(float width, float height, float currentValue) throws SlickException {
        this(width, height);
        
        this.value = currentValue;
    }

    @Override
    public void render(Graphics g, float x, float y) throws SlickException {
        this.x = x;
        this.y = y;
        
        // render rect and fill
        float cornerRadius = 0;
        g.setColor(new Color(0, 0, 0, .5f));
        g.fillRoundRect(x, y, width, height, (int) cornerRadius);
        g.setColor(Color.decode("#ffffbe"));
        g.drawRoundRect(x, y, width, height, (int) cornerRadius);
        
        // render line
        float lineX1 = this.lineX;
        float lineX2 = this.lineX + this.lineWidth;
        float lineY1 = this.lineY;
        float lineY2 = lineY1;
        g.setColor(Color.decode("#ffffbe"));
        g.drawLine(lineX1, lineY1, lineX2, lineY2);
        
        // render slider box
        float sBoxX = this.sliderBoxX;
        float sBoxY = this.sliderBoxY;
        float sBoxWidth = this.sliderBoxWidth;
        float sBoxHeight = this.sliderBoxHeight;
        g.setColor(new Color(223, 182, 105));
        g.fillRoundRect(sBoxX, sBoxY, sBoxWidth, sBoxHeight, (int) cornerRadius);
        g.setColor(Color.decode("#ffffbe"));
        g.drawRoundRect(sBoxX, sBoxY, sBoxWidth, sBoxHeight, (int) cornerRadius);
    }
    
    public void performAction(GameContainer gc, int mx, int my) throws SlickException {
        Input input = gc.getInput();

        this.sliderBoxWidth = Math.round(this.height / 2);
        this.sliderBoxHeight = Math.round(this.height / 2);
        this.sliderBoxY = Math.round(this.y + ((this.y + this.height) - this.y) / 2
                                 - (this.sliderBoxHeight / 2));
        
        this.lineWidth = Math.round(this.width - this.sliderBoxWidth);
        this.lineHeight = 1;
        this.lineX = Math.round(this.x + (this.sliderBoxWidth / 2));
        this.lineY = Math.round(this.y + ((this.y + this.height) - this.y) / 2);
        
        float mxRelative = mx - this.lineX;

        if (this.sliderBoxX < this.lineX - Math.round(this.sliderBoxWidth/2)) {
            this.sliderBoxX = this.lineX - Math.round(this.sliderBoxWidth/2);
        }
        else if(this.sliderBoxX > this.lineX + this.lineWidth + Math.round(this.sliderBoxWidth/2)) {
            this.sliderBoxX = this.lineX + this.lineWidth + Math.round(this.sliderBoxWidth/2);
        }
        
        // if is pressed somewhere on the line inside the sliderBox
        if (my >= this.y && my <= this.y + this.height) {
            if (input.isMousePressed(Input.MOUSE_LEFT_BUTTON)) {
                if ((mxRelative >= 0) && (mxRelative <= this.lineWidth)) {
                    this.percentage = (float) (mxRelative / this.lineWidth);
                    this.value = Math.round(this.percentage * 100);
                    this.sliderBoxX = mxRelative + this.lineX - Math.round(sliderBoxWidth / 2);
                }
            }
        }
        
        float minSliderBoxX = this.sliderBoxX;
        float minSliderBoxY = this.sliderBoxY;
        float maxSliderBoxX = this.sliderBoxX + this.sliderBoxWidth;
        float maxSliderBoxY = this.sliderBoxY + this.sliderBoxHeight;
        if ((mx > minSliderBoxX && mx < maxSliderBoxX) && (my > minSliderBoxY && my < maxSliderBoxY)) {
            if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
                wasSliderBoxDown = true;
            }
        }

        
        if(wasSliderBoxDown) {
            if (input.isMouseButtonDown(Input.MOUSE_LEFT_BUTTON)) {
                if (mxRelative >= 0 && mxRelative <= this.lineWidth) {
                    this.percentage = (float) (mxRelative / this.lineWidth);
                    this.value = Math.round(this.percentage * 100);
                    this.sliderBoxX = mxRelative + this.lineX - Math.round(sliderBoxWidth / 2);
                }
            } else {
                wasSliderBoxDown = false;
            }
        }
    }
    
    public void setInitialValues(float minValue, float maxValue) {
        if (this.minValue != minValue) {
            this.minValue = minValue;
        }
        if (this.maxValue != maxValue) {
            this.maxValue = maxValue;
        }
        
    }
    
    public float getValue() {
        return value;
    }

    public float getPercentage() {
        return percentage;
    }
}

holy s***, it seem it worked… wait… give me 5m to play with it and test it :o :slight_smile:

It is really working man!

“It’s floating point errors that are throwing the percentages off.”

Well, you know what, I would never ever think of this as a problem, even tho this problem was really pain, at least I learned some new things. Wish you a long life!

Solving problems is the funnest thing about coding. :wink: Glad you’re back on track. A long and happy life to you as well. 8)

@ctomni - I always see you go above and beyond to help other users. Excellent alternate example of how to implement a slider. ;D

Again me.
I totally forgot to tell you, about the initializing sliderBoxX before even all actions are performed on the slider.
So, I have this condition, because if I don’t want slider box x coordinate to be initialized with 0 value:

        // set sliderBoX X coordinate start position
        if (this.sliderBoxX < this.lineX - Math.round(this.sliderBoxWidth / 2)) {
            this.sliderBoxX = this.lineX - Math.round(this.sliderBoxWidth / 2);
            
            // set value & percentage on the min
            if (this.value != this.minValue) {
                this.value = this.minValue;
//                this.percentage = (this.value / this.maxValue) * 100.0f; // not needed
            }
        } 
        else if (this.sliderBoxX > this.lineX + this.lineWidth + Math.round(this.sliderBoxWidth / 2)) {
            this.sliderBoxX = this.lineX + this.lineWidth + Math.round(this.sliderBoxWidth / 2);
            
            // set value & percentage on the max
        }

I also have the method called setInitialValues (which initializes min & max value), this method is called inside of a update method before actionPerformed method, which is needed, because, I will almost always change min & max values during the game. However, we forgot to set properly values, I mean, slider works fine if we have for a minimum value 0, but if we have any other value greater than 0 set for the minimum value, slider will still perform actions between 0 and any other maxmimum value instead of that minimum value we have set (example 200-700). I’ve set the minValue on the value, but that does not affect on the calculations, since that value is not affected on the any of the code, so it makes no sense. :confused:

[quote=“CodeHead,post:18,topic:45435”]
Good help is hard to find these days. However, I don’t think you are completely out of the water yet in terms of helping out. For me, I am always trying to look for as much solutions to a problem as I can. Sometimes, you never know which form would come in handy… :point:

Thanks for the +1 and for helping out :).