[Java2D] How would I go for making buttons?

Hello all.

I’ve been looking everywhere for resources on how to create your own GUI system, and I can’t find anything good or helpful, nor can I find anything I can understand.

I’m working on a title menu for my game, and I need help writing a decent button class that can detect the mouse’s coordinates.

I have mouse input working, but I am having issues simply detecting an area of the screen. I have a point called msl (mouse location) that is controlled by my Input class. I then check for the point by doing something like this: [icode]if (msl == new Point(12, 12)) hover = true;[/icode]. That would only detect one pixel, how would I go for detecting a large area on the screen, similar to what I’d do with an array?

Sorry if this question sounds dumb or if my English isn’t spot on, but I really want help with this. I’m tired of text-based GUIs :(.

-Jev.

The mouse location is a point. The button [is/should be] a rectangle. Search for point-rectangle intersection tests. A button can (but likely shouldn’t) be any other shape, so infer what word you would change in the search terms if the button wasn’t a rectangle.

If you use a JButton for your button, you can put a MouseListener and/or MouseMotionListener within it. That listener will work purely locally for that component.

You can just set an image on the JButton or something like that. I remember I extended the JButton and overloaded or something the paint method? That works right? I forgot

Easiest way: First create a rectangle for the button area and then use the rectangles contains method.
Eg: if(rect.contains(msg)hover = true;

JButtons are messy in games :P.

Well its the title screen so it can’t hurt

From what I remembered from my java2d tower defence game what I did was have a button class (duh :stuck_out_tongue: ) and inside of it have a rectangle object for mouse collision. In the constructor I set the width and height and x and y. Then I added this to an arraylist in my main class which then, every update or repaint, iterates through that list and checks if the mouse is on it. If so, set the (there is a button image which is slightly transparent) whiteish rectangle behind the button to look like it is lighting up. Then, if clicked inside the rectangle, call the button’s click method which is overridden in the class the button is implemented in. I know that is very confusing but that is kinda how I remember it. Just try and do what Agro said instead of hard coding the buttons like I did. Unless you want a challenge :wink:

Wow, I never even considered doing a Rectangle.

I’ll try that here in a few guys.

Thanks,
Jev.

Still not working.

Here’s my code.


package net.kemoy.G581G.gui;

import java.awt.Graphics;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;

import net.kemoy.G581G.Art;
import net.kemoy.G581G.Core;

public class Button extends Rectangle {
	public static final long serialVersionUID = 1L;
	
	public Point mse = new Point(0, 0);
	BufferedImage button;
	
	public Button(String msg, int x, int y) {
		setBounds(x, y, Art.b1.getWidth(), Art.b1.getHeight());
		System.out.println(getBounds());
		button = Art.b1;
		String mval = msg;
		Graphics g = Core.display.getGraphics();
		
		g.drawImage(button, x, y, null);
		Fonts.drawString(mval, x + 20, y + 20);
	}

	public Button() {
		
	}
	
	public void tick() {
		if (contains(new Point(mse))) {
			button = Art.b2;
			System.out.println("Woohoo!");
		}
	}
}


It can detect the location and size now, but now I’m having issues with the mouse coordinates.

Is mse the mouse coords? B/c You never modify it…

‘mse’ is the point of the mouse coordinates that are collected in my input class.

Make sure that your mse and the rectangle you test for intersection are using the same coordinate system, i.e. J2D’s upper-left corner being 0,0 and y increasing downwards.

Also, I don’t see why you need to copy mse each tick(), unless you are trying to do some kind of thread safety, which if that is the case, you are going about it wrong.

Instead of extending rectangle i would rather just keep a field that contains rectangle

^This^

As Wesley said, your ‘mse’ variable is never updated (at least not in the code you posted). Your tick function is referencing the class level variable which is stuck at [0,0].

My approach to doing something like this is to create a base control class that contains the basic properties (visible, dimension, render point, etc) a “contains” function to check for mouse events, and an abstract render method then just extend/override the base class for your more specific classes. It may be a bit of an overkill if you’re just looking to add a couple of buttons to your main menu, but it makes implementing other controls such as progress bars, input controls, check boxes, and the like a lot easier (and more fun) in the long run. Your mileage may vary, but this approach has been serving me well while I’ve been developing my GUI library over the past couple of months. 8)

Edit: Just realized that your ‘mse’ variable is public. Are you updating this variable directly from outside of the button class? :o If that’s the case, have you tried tracing what value it’s set to on each tick? One other note, since ‘mse’ is already a Point, there’s really no need to create a new point in your tick method for the contains check. Just use the ‘mse’ variable directly.

It’s updated in my input class only when the mouse moves. It’s not being constantly set.


	public void mouseMoved(MouseEvent e) {
		if (game.state == 1) {
			butt.mse.x = e.getX();
			butt.mse.y = e.getY();
		} else if (game.state == 0) {

		}
	}
}

Oh wow, that was a huge mess-up.
I wasn’t modifying it, I thought I was.

I got everthing down, now I just need the detection going.

Here’s the current code I have. It’s not working.


	if (butt.contains(Button.mse)) {
		Button.button = Art.b2;
		System.out.println("Woohoo!");
	}

*Note, I moved the detection to the input class.

Why are you having “butt” do the bounds checking, and why are you suddenly referencing the member variables of your button class as if they’re static? Assuming “butt” is a globally referenced object (it’s hard to tell because your posted code doesn’t seem to contain all the important bits), shouldn’t the code be more along the lines of:


public void tick() {
    if (butt.contains(mse)) {
        button = Art.b2;
        System.out.println("Woohoo!");
    }
}

This would still appear to be incorrect since “butt” would have to have it’s bounds altered in creative ways each time it wanted to do a contains check with a control. In this scenario, you really want the button to determine whether the mouse point is currently inside of it instead of the input class being responsible for the contains check (like your earlier code had it):


public class Button extends Rectangle { 
   public Point mse = new Point(0, 0);
   BufferedImage button;
 
   /* ... SNIP ... */
   
   public void tick() {
      if (contains(butt.mse)) {
         button = Art.b2;
         System.out.println("Woohoo!");
      }
   }
}

Posting your input class would probably help to net some better answers.

It extends rectangle, and the contains method isn’t static.

Is tick() getting called? That’s the next thing to check if that code isn’t running.

Really, I think you should use the model that most gui systems use (including Swing names here, as you are probably familiar with them):

Component/Container - If you make the distinction, Container is a Component that can have child Components. Or you can just make all Components able to have children.

Components implement EventListeners - the EventListener methods do the handling of events

If there is a tick() or render() method on Component, it will call tick() or render() on all its children, and so on until the the whole scene graph is updated/rendered.

Might take a half hour to setup, and another to transfer, but I think you will have less headaches later, esp. if you go much further than buttons.

I’m assuming we’re talking about your input handler. The “static” comment was referring to this part of your code:


if (butt.contains(Button.mse))

“mse” would have to be a static member of the Button class for the above to make sense. Ideally, your Button class would look something like:


public class Button extends Rectangle {
    public static final long serialVersionUID = 1L;
    BufferedImage button;

    public Button(String msg, int x, int y) {
        setBounds(x, y, Art.b1.getWidth(), Art.b1.getHeight());
        System.out.println(getBounds());
        button = Art.b1;
        String mval = msg;
    }

    public render(Graphics g) {
        g.drawImage(button, x, y, null);
        Fonts.drawString(mval, x + 20, y + 20);
    }

    public boolean tick(Point mse) {
        if (contains(mse)) {
            // Change to the hovered
            button = Art.b2;
            System.out.println("Woohoo!");
            return true;
        }
        button = Art.b1;
        return false;
    }
}

Then your input handlers mouse move event would look something like this:


public void mouseMoved(MouseEvent e) {
    if (game.state == 1) {
        // Get a reference to your button here...we'll call it theButton in this case.
        theButton.tick(e.getPoint());
    } else if (game.state == 0) {
        // Do something else...
    }
}

The boolean on the tick event can be omitted if you’re only using one button. If you’re using more, then it becomes useful when iterating over multiple button objects (as in Slyth2727’s scenario) to determine if the event has been processed by a particular button thus ending the iteration loop.

BurntPizza’s recommendations are also solid and pretty much how my GUI controls are implemented as well. It adds a bit of complexity in the short term, but makes things far easier in the long term. Delving into the source for Java’s Component class may also provide some enlightenment/ideas for how to implement your controls.