Aller au contenu principal

Recette — Hub à boutons

Un hub vertical avec une card d'informations épinglée en haut et une liste de boutons défilante. Modèle typique : Clan Hub, Jobs Hub, menu principal.


Ce que tu vas construire

  • Card d'infos statique en haut (nom du clan, membres, rang)
  • Zone défilante de 5 boutons visibles (9 au total)
  • Scroll préservé au rafraîchissement

Code complet

package ca.monmod.client.screen;

import ca.tawess123.apocalyinterface.api.data.DataSource;
import ca.tawess123.apocalyinterface.api.layout.Card;
import ca.tawess123.apocalyinterface.api.layout.Column;
import ca.tawess123.apocalyinterface.api.layout.Row;
import ca.tawess123.apocalyinterface.api.layout.ScrollableColumn;
import ca.tawess123.apocalyinterface.api.layout.Spacer;
import ca.tawess123.apocalyinterface.api.screen.ApocalyScreen;
import ca.tawess123.apocalyinterface.api.theme.ApocalyDimensions;
import ca.tawess123.apocalyinterface.api.widget.Button;
import ca.tawess123.apocalyinterface.api.widget.Label;
import net.minecraft.network.chat.Component;

public final class ClanHubScreen extends ApocalyScreen {

// Données reçues du serveur via constructeur ou packet S2C
private String clanName;
private int memberCount;
private int maxMembers;
private int clanPoints;
private int clanRank;

// fixedHeight = 5 boutons × 20 px + 4 gaps × 4 px = 116 px
private static final int SCROLL_H =
5 * ApocalyDimensions.BUTTON_HEIGHT + 4 * Column.DEFAULT_GAP;

// Construit UNE SEULE FOIS dans le constructeur → scroll préservé au refresh
private final ScrollableColumn buttonScroll;

public ClanHubScreen(String clanName, int memberCount, int maxMembers,
int clanPoints, int clanRank) {
super(Component.translatable("monmod.screen.clanhub"), 260);
this.clanName = clanName;
this.memberCount = memberCount;
this.maxMembers = maxMembers;
this.clanPoints = clanPoints;
this.clanRank = clanRank;

// Construire la zone défilante dans le constructeur
buttonScroll = ScrollableColumn.builder().fixedHeight(SCROLL_H).build();
buttonScroll.add(Button.builder().text("monmod.btn.info").command("clan info").build());
buttonScroll.add(Button.builder().text("monmod.btn.classements").command("clan top").build());
buttonScroll.add(Button.builder().text("monmod.btn.rewards").command("clan rewards").build());
buttonScroll.add(Button.builder().text("monmod.btn.banque").command("clan bank").build());
buttonScroll.add(Button.builder().text("monmod.btn.ralliement").command("clan rally").build());
buttonScroll.add(Button.builder().text("monmod.btn.claim").command("clan claim").build());
buttonScroll.add(Button.builder().text("monmod.btn.abandon").command("clan abandon").build());
buttonScroll.add(Button.builder().text("monmod.btn.admin").command("clanadmin zone gui").build());
buttonScroll.add(Button.builder().text("monmod.btn.upgrades").command("clan upgrade").build());
}

@Override
protected void buildLayout(Column root) {
// Card épinglée en haut — reconstruite à chaque buildLayout,
// elle lit les champs d'instance courants
Card infoCard = Card.create();
infoCard.add(Label.builder()
.text("monmod.label.clan")
.value(DataSource.of(clanName))
.accent()
.build());
infoCard.add(Row.create()
.add(Label.builder()
.text("monmod.label.membres")
.value(DataSource.of(memberCount + " / " + maxMembers))
.build())
.add(Label.builder()
.text("monmod.label.rang")
.value(DataSource.of(clanRank > 0 ? "#" + clanRank : "—"))
.build()));
infoCard.add(Label.builder()
.text("monmod.label.points")
.value(DataSource.of(clanPoints + " pts"))
.muted()
.build());
root.add(infoCard);

root.add(Spacer.of(4));

// Zone défilante — ajoutée (pas recréée)
root.add(buttonScroll);
}

// Appelé depuis le handler de packet S2C
public void refresh(String name, int members, int max, int points, int rank) {
this.clanName = name;
this.memberCount = members;
this.maxMembers = max;
this.clanPoints = points;
this.clanRank = rank;
update(); // relayout, scroll préservé
}
}

Ouvrir l'écran

Depuis un packet S2C

// Handler du packet d'ouverture, côté client
Minecraft.getInstance().execute(() ->
Minecraft.getInstance().setScreen(
new ClanHubScreen(
payload.clanName(),
payload.memberCount(),
payload.maxMembers(),
payload.clanPoints(),
payload.clanRank()
)
)
);

Depuis un KeyMapping (debug)

if (OPEN_HUB_KEY.consumeClick()) {
Minecraft.getInstance().setScreen(
new ClanHubScreen("Les Loups", 8, 15, 4200, 2)
);
}

Clés i18n requises

{
"monmod.screen.clanhub": "Hub de Clan",
"monmod.label.clan": "Clan",
"monmod.label.membres": "Membres : %s",
"monmod.label.rang": "Rang : %s",
"monmod.label.points": "Points : %s",
"monmod.btn.info": "Mon Clan",
"monmod.btn.classements": "Classements",
"monmod.btn.rewards": "Récompenses",
"monmod.btn.banque": "Banque",
"monmod.btn.ralliement": "Ralliement",
"monmod.btn.claim": "Capturer zone",
"monmod.btn.abandon": "Abandonner",
"monmod.btn.admin": "Admin zone",
"monmod.btn.upgrades": "Améliorations"
}

Points clés à retenir

PointRaison
buttonScroll construit dans le constructeurPréserve l'offset de scroll entre les update()
DataSource.of(champ) dans buildLayoutLit la valeur courante du champ à chaque rebuild
.command("cmd") sur les boutonsEnvoie /cmd sans fermer l'écran — pattern recommandé
update() au lieu de init() dans refresh()Préserve le scroll contrairement à init()

Voir aussi