Skip to content

Creating a simple ClickGUI

lukflug edited this page Apr 23, 2023 · 7 revisions

Categories, modules, settings

To make a ClickGUI, you need to first describe the structure of your utility mod in a way that PanelStudio can comprehend. For that purpose, PanelStudio has certain interfaces to abstract this structure.

PanelStudio models the client structure as a hierarchy. At the top of the hierarchy is the IClient interface. All lower nodes implement the interface ILabeled, which represent an object with a name, optional description, which is either visible or invisible at any given time (this means any category, module, and setting can be hidden from the GUI at any point).

IClient itself is essentially a list of ICategory implementations. ICategory is an ILabeled with a list of IModule implementations. Utility mods typically implement module categories as an enum. A way this could be implemented would be:

public enum Category implements ICategory {
	COMBAT("Combat"),EXPLOITS("Exploits"),HUD("HUD"),MISCELLANEOUS("Miscellaneous"),MOVEMENT("Movement"),OTHER("Other"),RENDER("Render"),WORLD("World");
	public final String displayName;
	public final List<Module> modules=new ArrayList<Module>();
	
	private Category (String displayName) {
		this.displayName=displayName;
	}

	@Override
	public String getDisplayName() {
		return displayName;
	}

	@Override
	public Stream<IModule> getModules() {
		return modules.stream().map(module->module);
	}
}
public class Client implements IClient {
	@Override
	public Stream<ICategory> getCategories() {
		return Arrays.stream(Category.values());
	}
}

A module consists of a list of ISetting implementations. There exist multiple kinds of settings:

  • IBooleanSetting: an on/off toggle.
  • IColorSetting: an arbitrary color (with optional alpha and rainbow setting, and a choice whether to use RGB or HSB).
    • hasAlpha: controls whether the user can control alpha channel.
    • hasRainbow: control whether the user can toggle the rainbow option (not to be confused with getRainbow/setRainbow, which checks/controls whether the user actually enabled the rainbow).
    • hasHSBModel: controls whether the setting should be set via RGB or HSB.
    • getColor returns the current color setting ignoring rainbows, while setColor returns the current color setting including rainbow (if enabled).
  • IEnumSetting: may be one of multiple pre-determined finite named options (useful for different "modes" of operation).
  • IKeybindSetting: used to select a key from the user's keyboard.
  • INumberSetting: may be a number (either an integer, or a decimal number with a given number of decimal places) within a predetermined range.
    • getMaximumValue/getMinimumValue defines the range of allowed values (inclusively).
    • getPrecision gives the amount of decimal places.
  • IStringSetting: an arbitrary string of characters (e.g. for text boxes).

Some or all of those settings might correspond to a kind of setting in your setting system. Your setting classes should implement one of those interfaces (or if you do not want to pollute your setting system create a separate helper class that implements this interface an refers to an instance of a setting in your setting system). In many utlity mods, keybinds are a special, so you may want to create a IKeybindSetting (whose implementation might be a inner class or anonymous class) field in your Module class, with the keybind setting for your mod.

IModule has a method getSettings, which returns a stream of settings associated with that module. Each setting may also have sub-settings under it, which can be specified via the method getSubSettings (which by default returns null, i.e. no sub settings). Once you have specified the structure of your GUI this way, you can now turn to implementing the GUI itself.

Setting up the ClickGUI

Now you need to tell PanelStudio how to draw things. Luckily, the PanelStudio-MC library does most of this work. The main piece is a class extending the MinecraftGUI class (ClickGUI in the example mod, this class seems complicated, since it uses a lot of more advanced features discussed later):

public class ClickGUI extends MinecraftGUI {
	private final GUIInterface inter;
	private final GUI gui;
	public static final int WIDTH=120,HEIGHT=12,DISTANCE=6,BORDER=2;

	public ClickGUI() {
		IClient client; // initialize this variable with the structure describing your utility mod that you created in the previous section
		// The IInterface implementation defines how stuff is rendered. If you want to use a custom font with PanelStudio, overload the `drawString` and `getFontWidth` methods.
		inter=new GUIInterface(true) {
			@Override
			protected String getResourcePrefix() {
				return "examplemod:"; // replace this with the resource prefix of your utility mod that contains your GUI images
			}
		};
		// To define the GUI look and feel, you need a theme. You also need to tell the theme where to get Color Settings from.
		IColorScheme scheme=new IColorScheme() {
			HashMap<String,Color> colors = new HashMap<String,Color>();

			@Override
			public void createSetting (ITheme theme, String name, String description, boolean hasAlpha, boolean allowsRainbow, Color color, boolean rainbow) {
				// register a new color setting here (or if you don't want configurable colors, just store the `color` in a HashMap with `name` as the key)
				colors.put(name,color);
			}

			@Override
			public Color getColor (String name) {
				// you may need to replace this by getting the corresponding color setting from your setting manager (see above)
				return colors.get(name);
			}
		}
		// For now we're going to use the GameSense theme.
		ITheme theme=new GameSenseTheme(new ThemeScheme(Theme.GameSense),9,4,5,": "+TextFormatting.GRAY)

		// Defining function keys ...
		IntPredicate keybindKey=scancode->scancode==Keyboard.KEY_DELETE;
		IntPredicate charFilter=character->{
			return character>=' ';
		};
		ITextFieldKeys keys=new ITextFieldKeys() {
			@Override
			public boolean isBackspaceKey (int scancode) {
				return scancode==Keyboard.KEY_BACK;
			}

			@Override
			public boolean isDeleteKey (int scancode) {
				return scancode==Keyboard.KEY_DELETE;
			}

			@Override
			public boolean isInsertKey (int scancode) {
				return scancode==Keyboard.KEY_INSERT;
			}

			@Override
			public boolean isLeftKey (int scancode) {
				return scancode==Keyboard.KEY_LEFT;
			}

			@Override
			public boolean isRightKey (int scancode) {
				return scancode==Keyboard.KEY_RIGHT;
			}

			@Override
			public boolean isHomeKey (int scancode) {
				return scancode==Keyboard.KEY_HOME;
			}

			@Override
			public boolean isEndKey (int scancode) {
				return scancode==Keyboard.KEY_END;
			}

			@Override
			public boolean isCopyKey (int scancode) {
				return scancode==Keyboard.KEY_C;
			}

			@Override
			public boolean isPasteKey (int scancode) {
				return scancode==Keyboard.KEY_V;
			}

			@Override
			public boolean isCutKey (int scancode) {
				return scancode==Keyboard.KEY_X;
			}

			@Override
			public boolean isAllKey (int scancode) {
				return scancode==Keyboard.KEY_A;
			}			
		};
		// Define how settings get mapped into GUI widgets (we're going to stick with the default for now)
		IComponentGenerator generator=new ComponentGenerator(keybindKey,charFilter,keys);
		IComponentAdder classicPanelAdder=new PanelAdder(gui,false,true,title->title);
		ILayout classicPanelLayout=new PanelLayout(WIDTH,new Point(DISTANCE,DISTANCE),(WIDTH+DISTANCE)/2,HEIGHT+DISTANCE,animation,level->ChildMode.DOWN,level->ChildMode.DOWN,popupType);
		// Finally populate the GUI!
		classicPanelLayout.populateGUI(classicPanelAdder,generator,client,theme);
	}

	@Override
	protected GUI getGUI() {
		return gui;
	}

	@Override
	protected GUIInterface getInterface() {
		return inter;
	}

	@Override
	protected int getScrollSpeed() {
		return scrollSpeed; // replace with the value of your scroll speed setting
	}
}

You are now essentially done! You should now have an (almost) completely functional GUI. To enter the GUI, call the enterGUI method of your ClickGUI instance.

Saving panel configuration

Sometimes it might be desirable to store the position of individual panels and whether they are open or not in a file, so that when a user closes the game and opens it again later, the panels are in the same position and state. PanelStudio faclitates this with a couple of interfaces and methods.

  • IPanelConfig: describes how to load/store the state of an individual panel. The state consists of "position", "size", and "state". "state" is an arbitrary boolean value, but usually describes whether a panel is open or collapsed.
  • IConfigList: aggregrates the set of all IPanelConfig. Methods are: begin (to start loading/saving), end (to stop loading/saving), addPanel register a new IPanelConfig (intended for saving), getPanel acquire a existing IPanelLoading (intended for loading).

In particular you can pass the IConfigList to the loadConfig and saveConfig methods of your ClickGUI instance to perform loading/saving.

Clone this wiki locally