Own Component - ParzivalExe/guiapi GitHub Wiki

You have finally come to the end-boss, creating your own Components. In this Chapter I will first tell you how you can create your own component and give them a specific logic, then how to update this component so that it also supports XML.

Creating Component

Every Component you create must in some way inherit from Component, so your first step is to create a new Class and extend it from Component

public class ExecuteCommandComponent extends Component {

}

As you might already guess from the Class-Name, we want to create a Component, that will perform a specific Command for a Player when he clicks the Component.

Next we must create the Constructor for your new Component and also implement the parent constructor with a super-call.

public ExecuteCommandComponent(ComponentMeta meta) {
    super(meta);
}

Now, we must implement the one abstract function given in Component: componentClicked(...)

@Override
public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
    //Content
    gui.closeGui();
}

As you might have already guessed, this function is always called, when the Component is clicked, so this is the function where most of the logic must be implemented. So let's start with exactly that.

if(whoClicked instanceof Player) {

    if(command != null) {
        ((Player) whoClicked).performCommand(command);
    }else{
        whoClicked.sendMessage("This component has no Command implemented right now");
    }

}else{
    whoClicked.sendMessage("Normally, only players can click Components");
} 

Now, as you might already have noticed, command is not yet implemented. Therefore we also have to create a new field command

private String command;

and add the getter and setter for this field

public void setCommand(String command) {
    this.command = command;
}

public String getCommand() {
    return command;
}

It might also be a good idea to overhaul your constructor and create a completely new one so that we can create this component and give the command for it directly when we create it

public ExecuteCommandComponent(ComponentMeta meta, String command) {
    super(meta);
    this.command = command;
}

public ExecuteCommandComponent(ComponentMeta meta) {
    this(meta, null);
}

Great, we have now finished creating a component. You can test this component by adding it to the Gui like any other Component that comes default with GuiAPI.

ComponentMeta commandMeta = new ComponentMeta("GameMode 1", new ItemStack(Material.COMMAND));
ExecuteCommandComponent commandComponent = new ExecuteCommandComponent(commandMeta, "gamemode 1");
gui.addComponent(commandComponent);

The whole class should now look like this:

public class ExecuteCommandComponent extends Component{
    
    private String command;
    
    public ExecuteCommandComponent(ComponentMeta meta, String command) {
        super(meta);
        this.command = command;
    }

    public ExecuteCommandComponent(ComponentMeta meta) {
        this(meta, null);
    }

    public ExecuteCommandComponent() {
        this(new ComponentMeta("", new ItemStack(Material.WOOL)));
    }
    
    @Override
    public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
        if(whoClicked instanceof Player) {
            
            if(command != null) {
                ((Player) whoClicked).performCommand(command);
            }else{
                whoClicked.sendMessage("This component has no Command implemented right now");
            }
            
        }else{
            whoClicked.sendMessage("Normally, only players can click Components");
        }
    }

    public void setCommand(String command) {
        this.command = command;
    }

    public String getCommand() {
        return command;
    }
}

XML-Ready

Now, we just need to get this Component XML-Ready. As this is a very simple Component with only one string-field, this is very easy to implement.

Constructor

First you need to implement a public constructor without any arguments, because this is needed to create this Component through XML. So, we add the following constructor:

public ExecuteCommandComponent() {
    this(new ComponentMeta("", new ItemStack(Material.WOOL)));
}

XML-Attribute

Now, all you got to do is write @XMLAttribute above private String command.

The Annotation XMLAttribute does pretty much what it's name suggests. It makes any field into an attribute for XML-Code. This annotation also has got some variables we can change to customise this attribute further.

  1. necessary (boolean): With this you can specify whether this attribute is necessary for this Component to work. If this value is true and an ExecuteCommandComponent-Element in XML doesn't have this attribute implemented, the component will throw an Exception and not be created.
  2. attrName (String): specifies the name of this attribute. If this isn't set at all, the name will be the exact same as the field itself.
  3. defaultValue (String): specifies the default-value used if this attribute is not implemented as a String (because in xml values are always text/string before converted). This will be used when the attribute is not implemented. If you don't set the defaultValue and the attribute is not implemented, it will simply ignore this field completely.
  4. converter (Class<? extends Converter>): specifies a Converter. This converter is needed for any field that isn't a primitive-type (int, String, float, double, short, long, byte, char, boolean). While primitives are converted from the string given in XML automatically, any other object can't be created from this String. I will show more later under the Converter-Part of this Tutorial.
  5. forceEndType (Class<?>): specifies the EndType. This is normally simply the type of the given field and is not needed until further into the chapter by which case it will be clearer how this variable works.

Well, after looking at all these variables, I would suggest, that command is needed for this Component to work and should therefore be changed to necessary, so we must change @XMLAttribute to @XMLAttribute(necessary = true).

So, the implementation of your Component would right now look something like this:

<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode 1"/>

Now, let's start changing this up a bit more to bring more functions and more complexity to this Component so that I can show off some more options for XML.

XML-Constructor

Let's start by making command its own object. Here is the object we are going to start with:

public class Command {
    
    private String command;
    private ArrayList<String> arguments;

    public Command(String command, ArrayList<String> arguments) {
        this.command = command;
        this.arguments = arguments;
    } 
    public Command(String command) {
        this(command, new ArrayList<String>());
    }

    public String buildCommand() {
        StringBuilder command = new StringBuilder(getCommand());

        for(String argument : arguments) {
            command.append(" ").append(argument);
        }
        return command.toString();
    }

    //region Setter

    public void setCommand(String command) {
        this.command = command;
    }

    public void setArguments(ArrayList<String> arguments) {
        this.arguments = arguments;
    }

    public void addArgument(String argument) {
        arguments.add(argument);
    }
    public void addArgument(String argument, int index) {
        arguments.add(index, argument);
    }

    //endregion

    //region Getter

    public String getCommand() {
        return command;
    }

    public ArrayList<String> getArguments() {
        return arguments;
    }

    public String getArgumentAtIndex(int index) {
        return arguments.get(index);
    }

    //endregion

}

Now we also need to update the Command-Field

@XMLAttribute(necessary = true)
private String command;

... to ...

@XMLConstructor(constructorAttributes = {
    @XMLAttribute(necessary = true, attrName = "command", forceEndType = String.class),
    @XMLAttribute(attrName = "arguments", forceEndType = String.class)
})
private Command command;

As you see, we can also create Objects through their Constructor. What is important to realise here is, that we always need to specify the attrName for every single attribute because in Java-Reflections you simply can't get the name of a constructor-argument.

It is also important that every possible combination of these XMLAttributes needs to have a matching constructor. That's why I made command necessary = true, because now we have two possible combinations ...

<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode"/>

... and ...

<ExecuteCommandComponent title="GameMode 1" look="137" command="gamemode" arguments="[1]"/>

... and both of them have a matching constructor with ...

public Command(String command) {
    this(command, new ArrayList<String>());
}

... and ...

public Command(String command, ArrayList<String> arguments) {
    this.command = command;
    this.arguments = arguments;
} 

Would we make arguments necessary = true as well, we only needed the Constructor with two arguments while making command necessary = false we needed a third Constructor without any arguments.

In addition to that, you can also see, that for this the ForceEndType is needed, since the Constructor is picked from these Types but importantly, you can also see that for ArrayLists, Lists or Arrays you need to specify the inner type (for example: for ArrayList<String> the ForceEndType is String.

XML-Content

XMLContent is a annotation that is also used for fields, just like XMLAttribute and XMLConstructor but, as the name already suggests, this time to mark that the value associated with this field should be found as Content of the Element representing this Component. As an example the newOpenGui-Field for the Folder-Component.

@XMLContent(necessary = true)
private Gui newOpenGui;

As you can see, for this Annotation there is only one value: necessary.

The Content of this Element is created the same way as Components, meaning, you could create your very own Class and change it so it can become a XML-Element.

We could change command inside ExecuteCommandComponent from XMLConstructor to XMLContent IF we changed Command itself accordingly so that it could be created in XML. This would look something like this:

public class Command {

    @XMLAttribute(necessary = true)
    private String command;
    @XMLAttribute
    private ArrayList<String> arguments;

    public Command(String command, ArrayList<String> arguments) {
        this.command = command;
        this.arguments = arguments;
    }
    public Command(String command) {
        this(command, new ArrayList<String>());
    }
    public Command() {
        this(null);
    }
    ...
}

As you can see, we have to add the XMLAttribute-Annotation and add a Constructor without any arguments.

Now we only need to change the ExecuteCommandComponent accordingly:

public class ExecuteCommandComponent extends Component{

    @XMLContent(necessary = true)
    private ArrayList<Command> commands = new ArrayList<>();

    public ExecuteCommandComponent(ComponentMeta meta, ArrayList<Command> commands) {
        super(meta);
        this.commands = commands;
    }
    public ExecuteCommandComponent(ComponentMeta meta, Command command) {
        super(meta);
        if(command != null) {
            this.commands.add(command);
        }
    }
    public ExecuteCommandComponent(ComponentMeta meta) {
        this(meta, (Command) null);
    }
    public ExecuteCommandComponent() {
        this(new ComponentMeta("", new ItemStack(Material.WOOL)));
    }

    @Override
    public void componentClicked(HumanEntity whoClicked, Gui gui, InventoryAction action, int slot, ClickType clickType) {
        if(whoClicked instanceof Player) {

            if(commands != null || commands.size() > 0) {
                for(Command command : commands) {
                    ((Player) whoClicked).performCommand(command.buildCommand());
                }
            }else{
                whoClicked.sendMessage("This component has no Commands implemented right now");
            }

        }else{
            whoClicked.sendMessage("Normally, only players can click Components");
        }
        gui.closeGui();
    }

    public void addCommand(Command command) {
        this.commands.add(command);
    }
    public void addCommands(Collection<Command> commands) {
        this.commands.addAll(commands);
    }

    public void setCommands(ArrayList<Command> commands) {
        this.commands = commands;
    }

    public void clearCommands() {
        commands.clear();
    }

    public ArrayList<Command> getCommands() {
        return commands;
    }
}

As you can see, I have taken this opportunity to change up this component a bit as it is now able to perform several Commands.

For Implementation in Gui we must first Include the Command-Class and can then create the Component just like this:

<ExecuteCommandComponent title="Debug Setup" look="3x137" description="[Performs:, /gamemode 1, /time set 0, /weather clear]">
    <Command command="gamemode" arguments="[1]"/>
    <Command command="time" arguments="[set, 0]"/>
    <Command command="weather" arguments="[clear]"/>
</ExecuteCommandComponent>

You can also leave out all the Command-Elements and try it again and it should (because at least one Command-Element as Content is necessary) fail.

Converter

Lastly we come to the Converter. As an easy easy example, I will use the OpenOptionConverter which is used for AdditionalOptionsComponent.

public class OpenOptionConverter implements Converter {

    @Override
    public Object attributeStringToValue(String attrString, Object defaultValue) {
        if(attrString.equalsIgnoreCase("newInventory"))
            return OpenOption.NEW_INVENTORY;
        else if(attrString.equalsIgnoreCase("underInventory"))
            return OpenOption.UNDER_INVENTORY;
        else
            return defaultValue;
    }

}

As you can see, creating your own Converter is easy, as is using it. All you need to do is give his class as the variable converter of XMLAttribute:

@XMLAttribute(converter = OpenOptionConverter.class)
public OpenOption openOption = OpenOption.UNDER_INVENTORY;

And that's it. Now a String like openOption="NewInventory" will be changed to OpenOption.NEW_INVENTORY.

Ending

Now, that's it. YOU HAVE SURVIVED 🎊🎉

No really, if you really got to this point, that's one hell of an achievement. I know this API doesn't belong to the easiest to learn, but once you mastered it, you will never want to go without it, just like I never develop a Plugin without using it nowadays, which was kind of the idea in the beginning in general, because just making a UI was simply to complicated for me.

Well, you can now proceed to End & Help if you want to. If not, that is fully understandable 😅

Resources

Component-Class: io.github.parzivalExe.guiApi.components.Component
XML-Annotations: io.github.parzivalExe.guiApi.antlr.interfaces.*
Example-Component (in Java): io.github.parzivalExe.guiApi.components.ExecuteCommandComponent
Example-Command-Class (in Java): io.github.parzivalExe.guiApi.objects.Command

⚠️ **GitHub.com Fallback** ⚠️