Jump to content

[Tutorial] Implementing a GUI


liverare

Recommended Posts

Note: this is not how to make a GUI. This is how to implement one.

The following is assumed:

  • You have one file that contains your GUI code.
  • You have one file that contains your script code.
  • Your GUI code has a 'start' button which is public or has a getter function.

Let's start with your script code:

public class TestScript extends Script {

	@Override
	public void onStart() throws InterruptedException {
	}

	@Override
	public int onLoop() throws InterruptedException {
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
	}
}

Simple code with three routines: onStart, onLoop and onExit. Here's what we're going to do:

onStart

  1. Check the script parameters and store the values into Atomic variables. I'll explain why later.
  2. If we don't have script parameters, we then initialise the GUI and then show it.

onLoop

  1. If our Atomic variables are empty, then have the script do some afk things (shake the mouse) until we do.
  2. If the user closes the GUI and our Atomic variables are empty, then stop the script.

onExit

  1. If the GUI exists, then dispose of it.

Okay, so now let's begin by declaring some variables:

public class TestScript extends Script {

	private JFrame gui;
	private String params;
	private AtomicReference<String> treeName;
	private AtomicReference<String> axeName;
	private AtomicBoolean powerchop;
	private AtomicInteger stopAtLevel;

	@Override
	public void onStart() throws InterruptedException {
		initialiseVars();
	}

	public void initialiseVars() {
		params = super.getParameters();
		treeName = new AtomicReference<>();
		axeName = new AtomicReference<>();
		powerchop = new AtomicBoolean();
		stopAtLevel = new AtomicInteger();
	}

	@Override
	public int onLoop() throws InterruptedException {
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
	}
}

Notice how I haven't instantiated the 'gui' variable? This is intentional. GUIs are inefficient and memory intensive. If the user has started the script with script parameters, then just use that. However, if the script parameters are wrong, then use the GUI as backup.

Now let's check the script parameters and initialise the GUI.

Note: how you check script parameters are valid is up to you. You can check out my CLI Support Made Easy API API, Script File (ini) API, and OSBot File API.

public class TestScript extends Script {

	private JFrame gui;
	private String params;
	private AtomicReference<String> treeName;
	private AtomicReference<String> axeName;
	private AtomicBoolean powerchop;
	private AtomicInteger stopAtLevel;

	@Override
	public void onStart() throws InterruptedException {
		initialiseVars();
		if (!checkScriptParameters()) {
			SwingUtilities.invokeLater(this::initialiseGUI());
		}
	}

	private void initialiseVars() {
		params = super.getParameters();
		treeName = new AtomicReference<>();
		axeName = new AtomicReference<>();
		powerchop = new AtomicBoolean();
		stopAtLevel = new AtomicInteger();
	}
	
	private boolean checkScriptParameters() {
		boolean valid = false;
		// TODO check script parameters
		// TODO put values into Atomic variables (parse them if need be)
		// TODO return true/false based on whether the parameters were correct
		return valid;
	}

	private void initialiseGUI() {
		// TODO gui stuff
	}

	@Override
	public int onLoop() throws InterruptedException {
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
	}
}

So, about those Atomic variables. Notice the "SwingUtilities.invokeLater..." stuff? Well, you can read about Swing Utilities here. The short-form is: we're running the GUI on a separate thread. This means the following:

  • The client won't freeze whilst the GUI is open (yay!).
  • The Atomic variables are going to handle all that messy concurrency stuff that is now an issue because we're using a separate thread for stuff.

Without Atomic variables, we'd need to add our own thread-safety. Why? Because if your script changes a variable at the exact same moment your GUI decides to update that variable, the script thread and GUI threads will conflict and things break. Praise be to Atomic variables!

Also, if you're wondering what the :: is for, you can read about that here. The short and sweet: it's a way of referencing a method.

Okay now, let's do GUI stuff! Right, we're going to need to add a method that grabs user input from GUI and stores it into the Atomic references.

public class TestScript extends Script {

	private JFrame gui;
	private String params;
	private AtomicReference<String> treeName;
	private AtomicReference<String> axeName;
	private AtomicBoolean powerchop;
	private AtomicInteger stopAtLevel;

	@Override
	public void onStart() throws InterruptedException {
		initialiseVars();
		if (!checkScriptParameters()) {
			SwingUtilities.invokeLater(this::initialiseGUI());
		}
	}

	private void initialiseVars() {
		params = super.getParameters();
		treeName = new AtomicReference<>();
		axeName = new AtomicReference<>();
		powerchop = new AtomicBoolean();
		stopAtLevel = new AtomicInteger();
	}
	
	private boolean checkScriptParameters() {
		boolean valid = false;
		// TODO check script parameters
		// TODO put values into Atomic variables (parse them if need be)
		// TODO return true/false based on whether the parameters were correct
		return valid;
	}

	private void initialiseGUI() {
		gui = new MySexyGUI();
		gui.setLocationRelativeTo(bot.getCanvas());
		gui.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				TestScript.super.stop(false);
			}
		});
		gui.getStartButton().addActionListener(this::startButtonClicked);
		gui.setVisible(true);
	}

	private void startButtonClicked(ActionEvent e) {
		String treeName = gui.getTreeNameTextField().getText();
		String axeName = gui.getAxeNameTextField().getText();
		boolean powerChop = gui.getPowerChopCheckBox().isSelected();
		int stopAtLevel = (int) gui.getStopAtLevelNumberSpinner().getValue();
		if (treeName != null && !treeName.isEmpty() && axeName != null && !axeName.isEmpty()) {
			this.treeName.set(treeName);
			this.axeName.set(axeName);
			this.powerchop.set(powerChop);
			this.stopAtLevel.set(stopAtLevel);
			gui.setVisible(false);
		} else {
			// TODO let the user know there are problems in the GUI
		}
	}

	@Override
	public int onLoop() throws InterruptedException {
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
	}
}

Once we've grabbed the information from the GUI, we need to make sure it's correct. For things such as tree names, axe names, or any other constants, I'd have an enumerator that contains those values and let the user pick out them values from a combo box. It's a safer bet as the user can't pick a "wrong" tree and the enumerator can be used in the checking of the script parameters. If our values are correct, we then store them into the Atomic variables and then hide the GUI. We could dispose of the GUI at this point. However, if the GUI has already been initialised, you may as well keep it around so the user can make real-time changes to the behaviour of the script.

I've positioned the GUI relative to the bot's canvas and also added a WindowListener in the form of a WindowAdapter to the GUI that will stop the script if the user clicks on the close button. These are pretty expectant behaviour, so I'd figure I'd add them.

To recap, we've achieved the following:

onStart

  1. Check the script parameters and store the values into Atomic variables.
  2. If we don't have script parameters, we then initialise the GUI and then show it.

But also, we've achieved this as well:

onLoop

  1. If the user closes the GUI and our Atomic variables are empty, then stop the script.

Because the GUI has a listener that listens to the close button. If that button is clicked, the script will then stop. But we're not done with our onLoop just yet! We're going to want to figure out what values we're currently working with.

public class TestScript extends Script {

	private JFrame gui;
	private String params;
	private AtomicReference<String> treeName;
	private AtomicReference<String> axeName;
	private AtomicBoolean powerchop;
	private AtomicInteger stopAtLevel;

	@Override
	public void onStart() throws InterruptedException {
		initialiseVars();
		if (!checkScriptParameters()) {
			SwingUtilities.invokeLater(this::initialiseGUI());
		}
	}

	private void initialiseVars() {
		params = super.getParameters();
		treeName = new AtomicReference<>();
		axeName = new AtomicReference<>();
		powerchop = new AtomicBoolean();
		stopAtLevel = new AtomicInteger();
	}
	
	private boolean checkScriptParameters() {
		boolean valid = false;
		// TODO check script parameters
		// TODO put values into Atomic variables (parse them if need be)
		// TODO return true/false based on whether the parameters were correct
		return valid;
	}

	private void initialiseGUI() {
		gui = new MySexyGUI();
		gui.setLocationRelativeTo(bot.getCanvas());
		gui.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				TestScript.super.stop(false);
			}
		});
		gui.getStartButton().addActionListener(this::startButtonClicked);
		gui.setVisible(true);
	}

	private void startButtonClicked(ActionEvent e) {
		String treeName = gui.getTreeNameTextField().getText();
		String axeName = gui.getAxeNameTextField().getText();
		boolean powerChop = gui.getPowerChopCheckBox().isSelected();
		int stopAtLevel = (int) gui.getStopAtLevelNumberSpinner().getValue();
		if (treeName != null && !treeName.isEmpty() && axeName != null && !axeName.isEmpty()) {
			this.treeName.set(treeName);
			this.axeName.set(axeName);
			this.powerchop.set(powerChop);
			this.stopAtLevel.set(stopAtLevel);
			gui.setVisible(false);
		} else {
			// TODO let the user know there are problems in the GUI
		}
	}

	@Override
	public int onLoop() throws InterruptedException {
		String treeName = this.treeName.get();
		String axeName = this.axeName.get();
		if (treeName == null || treeName.isEmpty() || axeName == null || axeName.isEmpty()) {
			// TODO go afk
		} else {
			// TODO botty stuff
		}
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
	}
}

The 'onLoop' should be happy as long as you programmed your 'checkScriptParameters' function works. Why? Because if the user enters in wrong script parameters, then the GUI should show up to save the day. If the GUI doesn't show up, that's because the user has entered in correct script parameters, so 'treeName' and 'axeName' should both be valid.

Now, let's finish this by closing our GUI on the onExit:

public class TestScript extends Script {

	private JFrame gui;
	private String params;
	private AtomicReference<String> treeName;
	private AtomicReference<String> axeName;
	private AtomicBoolean powerchop;
	private AtomicInteger stopAtLevel;

	@Override
	public void onStart() throws InterruptedException {
		initialiseVars();
		if (!checkScriptParameters()) {
			SwingUtilities.invokeLater(this::initialiseGUI());
		}
	}

	private void initialiseVars() {
		params = super.getParameters();
		treeName = new AtomicReference<>();
		axeName = new AtomicReference<>();
		powerchop = new AtomicBoolean();
		stopAtLevel = new AtomicInteger();
	}
	
	private boolean checkScriptParameters() {
		boolean valid = false;
		// TODO check script parameters
		// TODO put values into Atomic variables (parse them if need be)
		// TODO return true/false based on whether the parameters were correct
		return valid;
	}

	private void initialiseGUI() {
		gui = new MySexyGUI();
		gui.setLocationRelativeTo(bot.getCanvas());
		gui.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				TestScript.super.stop(false);
			}
		});
		gui.getStartButton().addActionListener(this::startButtonClicked);
		gui.setVisible(true);
	}

	private void startButtonClicked(ActionEvent e) {
		String treeName = gui.getTreeNameTextField().getText();
		String axeName = gui.getAxeNameTextField().getText();
		boolean powerChop = gui.getPowerChopCheckBox().isSelected();
		int stopAtLevel = (int) gui.getStopAtLevelNumberSpinner().getValue();
		if (treeName != null && !treeName.isEmpty() && axeName != null && !axeName.isEmpty()) {
			this.treeName.set(treeName);
			this.axeName.set(axeName);
			this.powerchop.set(powerChop);
			this.stopAtLevel.set(stopAtLevel);
			gui.setVisible(false);
		} else {
			// TODO let the user know there are problems in the GUI
		}
	}

	@Override
	public int onLoop() throws InterruptedException {
		String treeName = this.treeName.get();
		String axeName = this.axeName.get();
		if (treeName == null || treeName.isEmpty() || axeName == null || axeName.isEmpty()) {
			// TODO go afk
		} else {
			// TODO botty stuff
		}
		return 250;
	}

	@Override
	public void onExit() throws InterruptedException {
		if (gui != null) {
			SwingUtilities.invokeLater(gui::dispose);
		}
	}
}

Boom. If the user stops the script whilst the GUI is open, the GUI will be dismissed. We've used SwingUtilities again because the GUI is still running on a separate thread, so we should avoid the script from interacting with it directly.

Have fun! Let me know if there are any problems.

 

I wrote this adhoc so there were a few things I missed out, which include:

  • Showing the GUI on 'initialiseGUI' (lol).
  • Hiding the GUI when you click on 'startButtonClicked', but only once you've validated the input from the GUI.
Edited by liverare
Oops forgot gui.setVisible(true); :)
  • Like 6
  • Heart 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...