Giter Club home page Giter Club logo

canvas's Introduction

canvas Build Status

A highly advanced and effective inventory management library for Bukkit plugins. The primary goal of canvas is to enable creation of elegant inventory systems without the quirks of existing libraries.

Feature Overview

Using canvas

canvas is integrated into plugins through the use of Maven.

Requirements

Then use the following command to install canvas to your local maven repository

git clone https://github.com/IPVP-MC/canvas.git
cd canvas/
mvn clean install

You will now be able to add canvas as a dependency in your pom.xml files with the following

<dependency>
    <groupId>org.ipvp</groupId>
    <artifactId>canvas</artifactId>
    <version>1.7.0-SNAPSHOT</version>
    <scope>compile</scope>
</dependency>

Note: You will need to use the Maven shade plugin in order to package your final .jar file. Add the following to your maven plugins section:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-shade-plugin</artifactId>
    <version>3.2.4</version>
    <executions>
        <execution>
            <phase>package</phase>
            <goals>
                <goal>shade</goal>
            </goals>
        </execution>
    </executions>
</plugin>   

see here for additional documentation on the shade plugin.

Once the dependency is registered, the only thing left to do is to register the MenuFunctionListener with the Bukkit event dispatcher.

Bukkit.getPluginManager().registerEvents(new MenuFunctionListener(), plugin);

Features

Menus

Out of the box, canvas supports the following inventory types as menus:

  • Chest menus
  • Hopper menus
  • Box menus (ie. 3x3 inventories such as workbench, dispenser, dropper)
    • Note: Due to an error in internal Minecraft code, shift clicking is disabled in Hopper and Box menus.

The above menus can be created by using the Builder pattern available in their respective classes. Creating a standard ChestMenu with 4 rows would look as such:

public Menu createMenu() {
    return ChestMenu.builder(4)
            .title("Menu")
            .build();
}

Displaying the menu to a player is made simple with Menu#open(Player)

public void displayMenu(Player player) {
    Menu menu = createMenu();
    menu.open(player);
}

Simple yet effective, our result looks like this:

Close handlers

Functionality when a Menu is closed can be added to Menus through the Menu.CloseHandler interface. The interface is meant to be used as a functional interface, and functionality is added elegantly with Java 8 lambda expressions.

Let's say we want to send the player some messages when they leave the inventory:

public void addCloseHandler(Menu menu) {
    menu.setCloseHandler((player, menu1) -> {
            player.sendMessage("You just closed the menu...");
            player.sendMessage("See you next time!");
    });
}

Redrawing

When switching between different Menus for players, by default the cursor will reset to the middle of the screen. This behavior can be changed to preserve cursor location at the cost of not being able to update Menu titles by enabling the redraw property of a Menu. When building a Menu via MenuBuilder, passing a value of true to MenuBuilder#redraw(boolean) enabled this functionality.

Note: If switching to a menu that has different dimensions, the redraw flag will be ignored and a new Inventory will be opened for the player, resetting their cursor.

Pagination

Creating connected pages of Menus to display a catalog of items is made easy with the PaginatedMenuBuilder class. The utility is able to be configured to set the proper previous and next page icons, and any necessary functions for items that are added.

In the basic example below, we create a simple menu displaying various static items.

Menu.Builder pageTemplate = ChestMenu.builder(3).title("Items").redraw(true);
Mask itemSlots = BinaryMask.builder(pageTemplate.getDimensions())
        .pattern("011111110").build();
List<Menu> pages = PaginatedMenuBuilder.builder(pageTemplate)
        .slots(itemSlots)
        .nextButton(new ItemStack(Material.ARROW))
        .nextButtonEmpty(new ItemStack(Material.ARROW)) // Icon when no next page available
        .nextButtonSlot(23)
        .previousButton(new ItemStack(Material.ARROW))
        .previousButtonEmpty(new ItemStack(Material.ARROW)) // Icon when no previous page available
        .previousButtonSlot(21)
        .addItem(new ItemStack(Material.DIRT))
        .addItem(new ItemStack(Material.GRASS))
        .addItem(new ItemStack(Material.COBBLESTONE))
        .addItem(new ItemStack(Material.STONE))
        // ...
        .build();

Per-player items and click handlers are supported for added items as well, via the PaginatedMenuBuilder.addItem(ItemStackTemplate) or PaginatedMenuBuilder.addItem(SlotSettings) methods.

If additional modifications need to be made to any newly created page that the builder doesn't support, adding functionality to modify a freshly created page is available by adding a Consumer<Menu> with the PaginatedMenuBuilder.newMenuModifier(Consumer<Menu>) method.

Slots

A Slot is exactly what you'd expect it to be, however canvas allows incredible customization of what they can do. Menus grant access to their slots through the Menu#getSlot(int) method.

There are 3 major pieces to Slot functionality:

Additionally, Slots grant the ability to render non-static items within their parent Menu via ItemStackTemplate (see below).

ClickOptions

Click options are the primary method of controlling what actions and click types can be performed on the raw item contents of the holding inventory. Two basic sets are provided with the library, which are ClickOptions.ALLOW_ALL and ClickOptions.DENY_ALL. By default, slots carry the DENY_ALL trait, denying all pickup and dropping off of items in the respective inventory. These behaviors are easily modified with the Slot#setClickOptions(ClickOptions) method.

Creation of custom options is done through the ClickOptions.Builder class. In the following example, we show you how to only allow dropping off of items into a specific slot, but not picking it up.

public void addClickOptions(Slot slot) {
    ClickOptions options = ClickOptions.builder()
            .allow(ClickType.LEFT, ClickType.RIGHT)
            .allow(InventoryAction.PLACE_ALL, InventoryAction.PLACE_ONE, InventoryAction.PLACE_SOME)
            .build();
    slot.setClickOptions(options);
}

ClickInformation

ClickInformation is a class constructed to provide the ClickHandler of a Slot with all available information about a click performed on the Slot. Also available is the possibility to change the resulting outcome of the click (whether interaction in the raw inventory occurs).

ClickHandler

Click handlers are where most of the logic of a slot will occur. As a slot is clicked, the click handler (if present) is triggered with information about who clicked as well as the click performed. The handler of a slot will always be triggered, regardless of whether or not the options of a slot forbid interaction with it. Keep in mind that the result of the click will be set by the options before the handler is triggered and as such the ClickInformation will represent this result.

Adding a handler is made simple with Slot#setClickHandler(ClickHandler):

public void addClickHandler(Slot slot) {
    slot.setClickHandler((player, info) -> {
        player.sendMessage("You clicked the slot at index " + info.getClickedSlot().getIndex());
        // Additional functionality goes here
    });
}

Templates

Item templates are used to render non-static items on a per-player basis. In certain situations, users of canvas may require a Menu to be updated because state has changed. For example, if an icon in a Menu displays the level of a player and the player has levelled up then the icon must be redrawn to reflect the changes. Item templates allow this functionality to happen without requiring a completely new Menu for each player.

Item templates are assigned to slots via the Slot#setItemTemplate(ItemStackTemplate) method. Alternatively,
Slot#setItem(ItemStack) can be used to set a static item that will render the same for every player.

Using the scenario above, where a player may be levelling up, code for an item may look something like the following:

Menu menu = ChestMenu.builder(1).title("Level").build();
Slot slot = menu.getSlot(4);
slot.setItemTemplate(p -> {
    int level = p.getLevel();
    ItemStack item = new ItemStack(Material.EXP_BOTTLE);
    ItemMeta itemMeta = item.getItemMeta();
    itemMeta.setDisplayName("Level: " + level);
    item.setItemMeta(itemMeta);
    return item;
});

With the item template set in place, every time the Menu is updated for the player using Menu.update(Player), the EXP bottle will be updated with the players current level and will be rendered in the inventory the player has open.

Masks

Masks create a layer of abstraction over raw inventory slot IDs. Through the usage of masks, populating specific slots inside an inventory has never been easier. Let's start with an example.

Suppose we begin with the previously created menu:

If we wanted to create a basic border of white glass on the outer slots, we would normally have to figure out which values reference those slots. These 22 slot IDs are 0, 1, 2, 3, 4, 5, 6, 7, 8, 17, 18, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35. If we didn't already store these values inside an int[] array, some general code might look something like this:

public void addWhiteBorder(Menu menu) {
    ItemStack glass = new ItemStack(Material.STAINED_GLASS_PANE);
    for (int i = 0 ; i < 9 ; i++) { // Setting first row
        menu.getSlot(i).setItem(glass);
    }
    menu.getSlot(17).setItem(glass);
    menu.getSlot(18).setItem(glass);
    for (int i = 26 ; i < 36 ; i++) {
        menu.getSlot(i).setItem(glass);
    }
}

As you can see, the code is fairly unintuitive and not at all friendly for refactoring or bug fixing and it only gets worse as the inventory size grows or we want to add more slots. What if we accidentally missed or forgot a slot id? Finding it would be a nuisance! For your benefit (or not) we've purposely excluded a single slot in the above example so that our inventory would have a gaping hole, feel free to see how long it would take to find the missing number.

Here is where Masks come in play. For the above inventory, masking the slots is made simple using a BinaryMask.

public void addWhiteBorder(Inventory inventory) {
    Mask mask = BinaryMask.builder(menu)
                    .item(new ItemStack(Material.STAINED_GLASS_PANE))
                    .pattern("111111111") // First row
                    .pattern("100000001") // Second row
                    .pattern("100000001") // Third row
                    .pattern("111111111").build(); // Fourth row
    mask.apply(menu);
}

Masks provide an incredibly simple interface for labelling slots. In the case of BinaryMask, each character represents a boolean value of whether or not the slot should be selected. A character value of '1' represents yes and all other characters the opposite. This model provides a semi-visual view of what the inventory will look like and is easy to add or remove specific slots.

The final product we end up with is:

Recipe Masks

A RecipeMask is another abstraction of mask which enabled assigning multiple types of items to slots. In the above image suppose we want every second item to be a red stained glass pane, we could use a RecipeMask in the following way:

public void addRedWhiteBorder(Inventory inventory) {
    Mask mask = RecipeMask.builder(menu)
            .pattern("wrwrwrwrw") // First row
            .pattern("r0000000r") // Second row
            .pattern("w0000000w") // Third row
            .pattern("rwrwrwrwr").build(); // Fourth row
    mask.apply(menu);
}

In the above, we use the w and r characters in the pattern wherever we want and then assign an item to that character. If no item is assigned, the character will default to AIR in the final product.

The final product we end up with is:

License

canvas is open source and is available under the MIT license.

canvas's People

Contributors

a248 avatar michaelowendyer avatar nubebuster avatar sainttx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

canvas's Issues

Error using dependency

Hi, I'm trying to use this api, but when I put the dependency in the dependencies tag it gives me an error, saying it can't find the dependency in the spigot repository, how can I solve it?
(in case you could write to me on discord, tag: codedevv)

Menu gets "lost" when opening from another menu

Hello,
i have following test-code which showcases my problem:

Menu test = ChestMenu.builder(1).build();
test.getSlot(0).setItem(new ItemStack(Material.GRASS_BLOCK));

Menu test2 = ChestMenu.builder(6).build();
test2.getSlot(5).setItem(new ItemStack(Material.DIAMOND));

test.setCloseHandler(((player, menu) -> {
        test2.open(player);
}));

test2.setCloseHandler(((player, menu) -> {
        test.open(player);
}));

test.open(e.getPlayer());

I would expect that both Inventories are alternating when closing them and both Items in both Menu's are not movable. But what happens here is that after the first close, the diamond-item is free movable and the closeHandler is not executed for the second Inventory. The first one works just fine.

am I using something wrong?
any ideas on this?
Thanks!

Questions and help

SlotSettings clickableItem = SlotSettings.builder()
.itemTemplate(
new StaticItemTemplate(new ItemStack(Material.ACACIA_BOAT));
)
.clickHandler((player, click) -> {
/* click logic goes here */
}).build();

With the following, why is it that you can pass Player and ClickInfo through the clickhandler but not Player through the itemTemplate? When I try to do

.itemTemplate(p -> {}) it informs me that it is not applicable for the argument p
yet when using the Slot.setItemTemplate method the Player argument is valid?

Can't allow dropping of items

I need to allow dropping of items
DROP_ALL_CURSOR, DROP_ONE_CURSOR

but even when I allow it, it doesn't work - only dropping slot works :(

Arrows not showing up

i run the following code but the change page items never appear, ever.

BinaryMask itemSlots = BinaryMask.builder(pageTemplate.getDimensions())
.pattern("000000000")
.pattern("000000000")
.pattern("111111111")
.pattern("111111111")
.pattern("111111111")
.pattern("000000000").build();
Collection listOfItems = new ArrayList();

	for (LoadoutItem item:loadout.getAllItems()) {
    	ItemStack builtItem = loadout.buildFormattedItemStack(item);
    	listOfItems.add(builtItem);
    }
	
	
	//list items, then return a collection of items for feeding ;)
	
	
	List<Menu> pages = PaginatedMenuBuilder.builder(pageTemplate)
			
	        .slots(itemSlots)
	        .addItems(listOfItems)
	        .nextButton(new ItemStack(Material.ARROW))
	        .nextButtonSlot(45)
	        .previousButton(new ItemStack(Material.ARROW)) 
	        .previousButtonSlot(36)  
	        .build();
	
	
	for (Menu menuMenu:pages) {
		addBorder(menuMenu);
		addLoadoutActionItems(menuMenu, sender);
	}
	return pages.get(page);

Documentation

I can't seem to find a documentation.
Where do I find it?

More Support

Hi! I'm so sorry for using issues to get support, do you have a discord?

Anyways, here is my issue. I'm using the following method to display a menu of a specific player's statistics controlled by a command. The only problem is, the first time the method is run, it hangs the server. Any subsequent usage of the method is lag-free.

public void showProfile(ElytraPlayer viewer, ElytraPlayer target) { ChestMenu menu = ChestMenu.builder(6).redraw(true).title(AuriUtils.colorString("&f&l" + target.getName() + "'s &c&lProfile")).build();

	BinaryMask itemSlots = BinaryMask.builder(menu.getDimensions())
			.item(new ItemBuilder(Material.GRAY_STAINED_GLASS_PANE).setDisplayName("").build())
			.pattern("011111111")
			.pattern("000100000")
			.pattern("000100000")
			.pattern("000100000")
	        .pattern("000100000")
	        .pattern("000100000").build();
	
	menu.getSlot(1, 1).setSettings(SlotSettings.builder().itemTemplate(p -> {
		return new ItemBuilder(Material.BARRIER).setLore("",AuriUtils.colorString("&7Click to close!")).setDisplayName(AuriUtils.colorString("&cClose")).build();
	}).clickHandler((p, c) -> {
		p.closeInventory();
	}).build());
	
	menu.getSlot(3, 2).setSettings(SlotSettings.builder().itemTemplate(p -> {
		ElytraPlayer ep = target;
		ArrayList<String> lore = new ArrayList<>();
		lore.add(AuriUtils.colorString(""));
		lore.add(AuriUtils.colorString("&c" + ep.asBukkitPlayer().getName() + "&7's Main Network statistics"));
		lore.add(AuriUtils.colorString(""));
		lore.add(AuriUtils.colorString("&7[ " + ep.getProgressBar() + "&7 ] &7&l" + "&c" + ep.getExperience() + "&7/" + ep.getRequiredXPToNextLevel() + " &c❂"));
		lore.add(AuriUtils.colorString(""));
		lore.add(AuriUtils.colorString("&7You are level &c" + ep.getLevel() + "!&7"));
		
		return new ItemBuilder(Material.LEGACY_SKULL_ITEM, (short) 3).setHead(p.getName()).setLore(lore).setDisplayName(AuriUtils.colorString("&c&l" + p.getName() + " &c[&7" + ep.getLevel() + "&c]" + "&7 's Profile")).build();
	}).clickHandler((p, c) -> {
		//row, collum
	}).build());
	
	menu.getSlot(5, 2).setSettings(SlotSettings.builder().itemTemplate(p -> {
		ElytraPlayer ep = target;
		ArrayList<String> lore = new ArrayList<>();
		lore.add(AuriUtils.colorString(""));
		lore.add(AuriUtils.colorString("&c" + ep.asBukkitPlayer().getName() + "&7's Balance:"));
		lore.add(AuriUtils.colorString(""));
		lore.add(AuriUtils.colorString("&7Your balance: &6" + PlaceholderAPI.setPlaceholders(ep.asBukkitPlayer(), "%vault_eco_balance_commas% ⛃")));
		if (RewardController.get().hasRewards(ep)) {
			lore.add(AuriUtils.colorString(""));
			lore.add(AuriUtils.colorString("&e&lYou have rewards!&7 Do /rewards to claim them!"));
		}
		
		return new ItemBuilder(Material.SUNFLOWER).setLore(lore).setDisplayName(AuriUtils.colorString("&c&l" + p.getName() + " &c[&7" + ep.getLevel() + "&c]" + "&7 's Balance")).build();
	}).clickHandler((p, c) -> {

	}).build());
	
	
	itemSlots.apply(menu);
	
	menu.open(viewer.asBukkitPlayer());
}`

Anyways, i realize a better way to do this would be to store a private ChestMenu in my GUIController (which provides this method) but then i would not be able to specify the 2nd player(target). Is there a better way of doing this or am i doing this correctly?

Container title doesn't update when using redraw

I have a system of menus that use canvas, some use redraw and some do not. Redraw is true on my paginated menus, but not on the static screens like the main menu.

My issue is that when a click handler opens a different (redraw enabled) menu from a non redraw menu, the title does not update.

I might be using it wrong but I don't think redraw menus should redraw over top of non redraw menus (AbstractMenu:114 only checks the menu it's opening for redraw, not the one its drawing over) Sidenote: In my particular case adding the open.isRedraw() fixed it

Whether that's intended behavior or not I would like to make sure that the containers title changes when the new menu opens.

Thanks in advance

Menu template from config

Example config struct(in hocon format):

shop {
  inventory {
    title = "&0Shop"
    rows = 4
  }

  matrix = [
    "____e____"
    "__m___d__"
    "_ABC_EFG_"
    "_D___H___"
  ]

 items {
    "e" {
      type = ENDER_CHEST
      name = "&eКейс с косметикой"
      lore = [
        "&fPrice: &e{price}"
        "&fOne more line"
      ]
    }
    etc for each char
  }
  }

_ by default air

Cannot get player specific slots in close handler.

It seems like that a player is removed from the menu before the close handler is triggered:

MenuHolder holder = (MenuHolder) currentInventory;
holders.remove(holder);
if (triggerCloseHandler) {
    getCloseHandler().ifPresent(h -> h.close(viewer, this));
}

which prevents you from using things like Slot::getItem(player) inside of close handlers.
Would just swapping around the close handler and removing the holder work and fix this?

Getting items from player

Hi, I really like the ability to select which actions can player do (like put items inside but donť take them out etc) - but I have problem with getting items which players put inside.

I have created simple empty ChestMenu and allowed putting items in. Then I created closeHandler for it which prints all slots.

But even if I put items inside this ChestMenu, when I get items from slots, all items are null.

How can I get items which I put inside ?

Force Redraw Menu (NOT A BUG)

When i have a paginated menu open, players clicking on a button does not redraw the page. I worked around this by causing the player to close the inventory first, and then re-open the menu, but this will reset the page the player is viewing. Is there a built in method to redraw the page for the player on clicking a dynamic button?

Library not working?

I've been trying to use this library, and setting itemstacks in the gui works fine, but no matter how many ways I try, I get no response on any click handlers. I'm using the latest paper jar.

iPvP

Hello.

I would like to ask if then iPvP server is still in development and if not, would it be possible to buy the domain? Please contact me on Discord (lukeeey#2013) to speak more about this.

Thanks.

Issue on startup

I did a clean install of canvas, then added it as a depend to my pom.xml. however, it still throws a noclassdeferror

java.lang.NoClassDefFoundError: org/ipvp/canvas/MenuFunctionListener
at main.java.com.elytraforce.loadoutsystem.Main.registerListeners(Main.java:39) ~[?:?]
at main.java.com.elytraforce.loadoutsystem.Main.onEnable(Main.java:26) ~[?:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:264) ~[server.jar:git-Spigot-79a30d7-acbc348]
at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:337) [server.jar:git-Spigot-79a30d7-acbc348]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:403) [server.jar:git-Spigot-79a30d7-acbc348]
at org.bukkit.craftbukkit.v1_12_R1.CraftServer.enablePlugin(CraftServer.java:381) [server.jar:git-Spigot-79a30d7-acbc348]
at org.bukkit.craftbukkit.v1_12_R1.CraftServer.enablePlugins(CraftServer.java:330) [server.jar:git-Spigot-79a30d7-acbc348]
at net.minecraft.server.v1_12_R1.MinecraftServer.t(MinecraftServer.java:422) [server.jar:git-Spigot-79a30d7-acbc348]
at net.minecraft.server.v1_12_R1.MinecraftServer.l(MinecraftServer.java:383) [server.jar:git-Spigot-79a30d7-acbc348]
at net.minecraft.server.v1_12_R1.MinecraftServer.a(MinecraftServer.java:338) [server.jar:git-Spigot-79a30d7-acbc348]
at net.minecraft.server.v1_12_R1.DedicatedServer.init(DedicatedServer.java:272) [server.jar:git-Spigot-79a30d7-acbc348]
at net.minecraft.server.v1_12_R1.MinecraftServer.run(MinecraftServer.java:545) [server.jar:git-Spigot-79a30d7-acbc348]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_242]

please help :)

A InventoryAction is not been caught by MenuFunctionListener

It could be a tiny problem...

Players in creative mode can pickup 64 items (middle click) from a menu.

InventoryAction.CLONE_STACK seems to be ignored by MenuFunctionListener.

Maybe giving users the chance to handle it makes this project more excellent, thanks!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.