Skip to content

update: v4 - full rewrite#113

Draft
LichtHund wants to merge 53 commits intomasterfrom
update/v4
Draft

update: v4 - full rewrite#113
LichtHund wants to merge 53 commits intomasterfrom
update/v4

Conversation

@LichtHund
Copy link
Member

@LichtHund LichtHund commented May 4, 2024

This update is a full re-write of the library, everything will change, so expect big breaking changes.
Things of note:

  • Spigot support will be dropped - Spigot has been in a stale state for a while now and I see no point in keeping supporting it as it has been more headache than it is worth.
  • Re-write is made with the intent to support multiple platforms other than Paper.
  • v3 will be put in a separate branch where it can still be kept up to date with updates but no new features will be added.

Try it out

This is very early developments so many features are missing and many bugs might be present, use only for testing purposes.

maven("https://repo.triumphteam.dev/snapshots")
implementation("dev.triumphteam:triumph-gui-paper:4.0.0-SNAPSHOT")

I have started working on docs for the new version: https://triumphteam.dev/docs/4.x.x/triumph-gui/introduction

Re-write status

The re-write is a decent place, but major changes can still happen.
Things that are left are:

  • System to update the GUI title.
  • System to handle event cancelling, right now all clicks are always cancelled.
  • System to support "storage" like components, where players can freely take and add items to the inventory.
  • Click information, currently all you get is the player.
  • Pagination, and scrolling.
  • Slot click actions.
  • Player inventory.
  • Combined inventory (container + player inventories).
  • Improved item builder.

Goals

The main goal of the update is to modernize the library, make it more powerful and allow for more customizability.
One of the biggest issues the library had was its "update" system, updating items in the inventory was always the main support question and the most confusing for most users.

v4 - Concepts and examples

This update took a lot of inspirations from other things, such as Compose (mostly), React, and Noxcrew's interfaces.

GUI View

Differently from the previous version, the GUI itself is only a blue-print for views, the view is what the player will see and interact with. The view is limited to a single viewer, you can show two different views for 2 different players that are identical, but never have 2 players seeing the same view. The view only exists while the GUI is opened.

Component

Starting of with the most important concept, a GuiComponent. Similar to React it describes how things will be rendered, in this case rendering means adding items to the Inventory.
A component will take note of what items was added to the inventory and when re-rendering it'll replace them.
Think of a component as a container of items that can be freely changed, once a change happens all items are re-added into their location.

Simple Example:

.component(component -> {
   
    component.render((container, player) -> {
        
        container.set(Slot.of(2, 2), item);
    }); 
})

When this component is rendered it'll set the item onto the slot 2, 2 (10 on a normal chest inventory).

Container

Simply put, the container is a holder for the items and their slots. There are different types of containers, such as Chest, Crafting, etc. The only difference about them is the mapping of the slots.

State

A "state" will control when a component re-renders.
There are two types of state:

  • State - Empty, does not contain any value, all it contains is a way to manually trigger a re-render by doing state.trigger().
  • MutableState - Stores any value and triggers a re-render when the value changes.

A mutable state also depends on a StateMutationPolicy, this will tell the state how it handles equality. When setting a new value to the state, if the value is equal (based on the policy), it'll NOT trigger a re-render of the component. The built in policies are:

  • ReferenceEquality - If the updated value is the equal reference wise it'll NOT trigger a re-render. Think of it like using old == new.
  • StructuralEquality - If the updated value is the equal structurally wise it'll NOT trigger a re-render. Think of it like using old.equals(new). This is the default behavior.
  • NeverEqual - No matter the value set, it'll always trigger a new re-render.

Using a state:

.component(component -> {

    // Create a mutable state with the starting value of "0"
    // This also binds the state to this component
    final var state = component.state(0);

    component.render((container, player) -> {
        container.set(
            Slot.of(2, 2),
            // Display the state as the item's name by using `state.getValue()`
            ItemBuilder.from(Material.POTATO).name(Component.text("State -> " + state.getValue())).asGuiItem((player, context) -> {
                // Increment the state value by 1
                // This will trigger the component to re-render, meaning that this item will be re-made
                state.setValue(state.getValue() + 1);
            })
        );
    });
})

A state does not need to be made within a functional component, it can be created outside, stored, re-used, etc. If a state is shared between two components it'll cause both to re-render. A state can also be shared between views, meaning you can trigger re-renders from one view to another.

Extending

The library is built around being able to change quite a lot of behaviors. For example, a GuiComponent by itself does nothing, it is paired with a GuiComponentRenderer, which can be changed for each GUI. This allows you to have completely different implementations of components, from Kotlin's coroutine's suspending components, to Completable future components, and much more.
The same process can be applied to clicks, a click action by itself does nothing and it uses a ClickHandler to implement how a click is run, so suspending clicks, async clicks, etc, are all possible.

Feedback is very important, so feel free to comment any addition, or change you'd like to see!

@LichtHund LichtHund added this to the v4 milestone May 4, 2024
@LichtHund LichtHund marked this pull request as draft May 4, 2024 20:00
@LoJoSho
Copy link

LoJoSho commented May 20, 2024

Can't wait to try this rewrite out once it is released! Glad yall are also dropping Spigot support, doesn't make sense since most servers are already running Paper!

@xMrAfonso
Copy link

Can't wait to try this rewrite out once it is released! Glad yall are also dropping Spigot support, doesn't make sense since most servers are already running Paper!
You can already try it out and report any issues you find. There is already a public snapshot.

@EncryptSL
Copy link

I see container have set function but what addItem or something like that ?

Is planned add this feature or this missing for now.

@LichtHund
Copy link
Member Author

I see container have set function but what addItem or something like that ?

Is planned add this feature or this missing for now.

What usage for that? Asking because there might be a replacement feature already.

@EncryptSL
Copy link

In example you can see i use container.set(slot, item...) But what if i want only addItem without set()..
I must add variable slot for get number, i think for this example is good addItem.

Now i don't know if is behaviour of asGuiItem { player, context -> } only in this snapshot. If i want simply asGuiItem() without funcion this not allow me add.

        gui.component { component ->
            component.render { container, player ->
                var slot = 0
                for (material in Material.entries) {
                    if (!config.contains("level.ores.${material.name}")) continue
                    container.set(slot, ItemBuilder.from(itemStack.create()).asGuiItem { player, context -> })
                    slot++
                }
            }
        }.build().open(player)

@Keylessboi
Copy link

Are there updated docs yet?

@LichtHund
Copy link
Member Author

LichtHund commented May 21, 2024

In example you can see i use container.set(slot, item...) But what if i want only addItem without set().. I must add variable slot for get number, i think for this example is good addItem.

Now i don't know if is behaviour of asGuiItem { player, context -> } only in this snapshot. If i want simply asGuiItem() without funcion this not allow me add.

        gui.component { component ->
            component.render { container, player ->
                var slot = 0
                for (material in Material.entries) {
                    if (!config.contains("level.ores.${material.name}")) continue
                    container.set(slot, ItemBuilder.from(itemStack.create()).asGuiItem { player, context -> })
                    slot++
                }
            }
        }.build().open(player)

I'll give this some thought, you can play around with Layouts as they can provide something similar.

@LichtHund
Copy link
Member Author

Are there updated docs yet?

Not yet, as this is too early in development and a lot of stuff might change, so keeping docs up to date would be a nightmare. I will add some examples at some point though.

@SamB440
Copy link

SamB440 commented May 24, 2024

The pom seems to be including annotations, guava, adventure, slf4j as compile. This was leading to large jars - I had to set the dependency to isTransitive = false and manually depend on core and nova as well.

Also, loving the look of this so far, very happy that it will be multi-platform now. I am willing to add support for Sponge (I actually have a working version already).

One thing I would like to see is stuff like component.render not requiring inclusion of the player argument, I'm just naming the player variable something random as it's useless to me, but it looks a bit ugly. (I guess Java 22 improves this with unnamed variables?)

This was referenced Dec 8, 2024
@ImNotStable
Copy link

ImNotStable commented May 15, 2025

Any plans on adding char-structured inventories?

final var gui = Gui.of(1)
        .title(Component.text("My Simple GUI!"))
        .charStructure("#########", "#1234567#", "#########")
        .statelessComponent(container -> {
            container.setItem('#', ItemBuilder.from(Material.DIAMOND)
                    .name(Component.text("Click me!"))
                    .asGuiItem((player, context) -> {
                        player.sendMessage("You have clicked on the diamond item!");
                    })
            );
        })
        .build();

@LichtHund
Copy link
Member Author

Hi @ImNotStable, no there is no plan to add that, I personally don't like it. However you can very easily do something like that using the GuiLayout. I did something similar as an example not too long ago, example is in Kotlin sorry but it'd be very similar in Java https://pastes.dev/T8FI7cuAqG.

@ImNotStable
Copy link

Not too familiar with Kotlin, but I appreciate the response. Just wanted to know for future use, I did take a look at GuiLayout and didn't find it to fit what I needed. Good luck with the development of v4!

@darksaid98
Copy link

How is v4 coming along? Is there anything we can do to help expedite the process other than testing?

I'd be willing to PR some GitHub actions to automate publishing snapshots and release artifacts if you're interested.

A release workflow would help avoid situations like on v3, where 3.1.12 was released. Yet, the only indication of its existence is that the Maven artifact was deployed, as there is no corresponding GitHub tag or release.

Since you're already using conventional commits, you could even auto-generate changelogs into something more organized like this.

@LichtHund
Copy link
Member Author

Hi @darksaid98! It's coming along pretty well, it is at a point where I want to do a full release soon. Only a few things I need to work on, like some better versioning for things that need it. I also would like to first finish the documentation of it before the full release.

As for workflow I definitely need to start using it more often, I don't think I'll do it for snapshots but I'll make it auto release to the repo when a github release is done so it forces me to do it i forget a lot.

@darksaid98
Copy link

Hi @darksaid98! It's coming along pretty well, it is at a point where I want to do a full release soon. Only a few things I need to work on, like some better versioning for things that need it. I also would like to first finish the documentation of it before the full release.

As for workflow I definitely need to start using it more often, I don't think I'll do it for snapshots but I'll make it auto release to the repo when a github release is done so it forces me to do it i forget a lot.

Sounds awesome. I have extensive experience with GH Actions and Maven Repositories, not to mention a lot of boilerplate lying around, so let me know if you'd like help with anything in that regard!

Maven Central now easily allows you to publish snapshots to the snapshot repository, so I'd recommend publishing artifacts whenever there are successful builds on the main branch.

And then, like you said, have a workflow on release publication, which builds artifacts and attaches them to the GH release, and publishes the release artifacts to the Maven Repository using a Gradle plugin like this (Which would be my recommendation).

At the end of the day, it comes down to automating a lot of manual and easily forgettable tasks that would otherwise slow you down or cause issues for library consumers.

@illyrius666
Copy link

bump

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.