Recette — Écran multi-panneaux
Un écran avec trois panneaux côte à côte : une boutique (liste cliquable), un panneau de détails (informations sur l'article sélectionné), et un inventaire (items du joueur). Modèle typique : interface de marchand, comparateur d'articles, dashboard multi-sections.
Ce que tu vas construire
┌──────────────┐ ┌────────────────────┐ ┌─────────────┐
│ Boutique │ │ Détails │ │ Inventaire │
│ [Recherche] │ │ Article : Diamant │ │ Solde : ... │
│ Diamant │ │ Prix : 250 ⬡ │ │ [Item1] │
│ Émeraude │ │ Stock : 12 │ │ [Item2] │
│ Or │ │ [Acheter] │ │ ... │
│ Netherite │ │ [Vendre] │ │ │
└──────────────┘ └────────────────────┘ └─────────────┘
Code complet
package ca.monmod.client.screen;
import ca.tawess123.apocalyinterface.api.data.DataSource;
import ca.tawess123.apocalyinterface.api.layout.Column;
import ca.tawess123.apocalyinterface.api.layout.Row;
import ca.tawess123.apocalyinterface.api.screen.ApocalyMultiScreen;
import ca.tawess123.apocalyinterface.api.screen.PanelConfig;
import ca.tawess123.apocalyinterface.api.theme.ApocalyColors;
import ca.tawess123.apocalyinterface.api.widget.Button;
import ca.tawess123.apocalyinterface.api.widget.ClickableList;
import ca.tawess123.apocalyinterface.api.widget.ItemIcon;
import ca.tawess123.apocalyinterface.api.widget.KeyValueRow;
import ca.tawess123.apocalyinterface.api.widget.Label;
import ca.tawess123.apocalyinterface.api.widget.ProgressBar;
import ca.tawess123.apocalyinterface.api.widget.SearchField;
import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import java.util.List;
public final class MarchandScreen extends ApocalyMultiScreen {
// Catalogue de la boutique — en production, reçu via packet S2C
private static final List<String> CATALOGUE = List.of(
"Diamant", "Émeraude", "Lingot d'or", "Netherite",
"Étoile du Nether", "Œil d'Ender", "Perle d'Ender", "Blaze Rod"
);
// État partagé entre les panneaux
private final String[] articleSelectionne = { CATALOGUE.get(0) };
private final String[] searchQuery = { "" };
public MarchandScreen() {
super(Component.translatable("monmod.screen.marchand"));
}
@Override
protected List<PanelConfig> buildPanels() {
return List.of(
PanelConfig.of(
Component.translatable("monmod.panel.boutique"),
140,
this::buildBoutiquePanel),
PanelConfig.of(
Component.translatable("monmod.panel.details"),
170,
this::buildDetailsPanel),
PanelConfig.of(
Component.translatable("monmod.panel.inventaire"),
120,
this::buildInventairePanel)
);
}
// ── Panneau gauche : boutique ─────────────────────────────────────────────
private void buildBoutiquePanel(Column col) {
col.add(SearchField.builder()
.placeholderKey("monmod.search.articles")
.onChange(text -> { searchQuery[0] = text; })
.maxLength(20)
.build());
List<String> filtres = CATALOGUE.stream()
.filter(s -> s.toLowerCase().contains(searchQuery[0].toLowerCase()))
.toList();
var font = Minecraft.getInstance().font;
col.add(ClickableList.<String>builder()
.items(filtres)
.visibleRows(6)
.rowHeight(14)
.rowRenderer((gfx, item, x, y, w, mx, my) ->
gfx.drawString(font, item, x + 3, y + (14 - 9) / 2,
ApocalyColors.TEXT, false))
.onSelect(item -> {
articleSelectionne[0] = item;
init(); // reconstruire pour mettre à jour le panneau détails
})
.build());
}
// ── Panneau centre : détails ──────────────────────────────────────────────
private void buildDetailsPanel(Column col) {
col.add(Label.builder()
.text("monmod.label.article")
.value(DataSource.of(articleSelectionne[0]))
.title()
.build());
col.add(KeyValueRow.builder()
.key("monmod.row.prix")
.value(DataSource.of("250 ⬡"))
.build());
col.add(KeyValueRow.builder()
.key("monmod.row.stock")
.value(DataSource.of("12"))
.build());
col.add(KeyValueRow.builder()
.key("monmod.row.vendeur")
.value(DataSource.of("Villageois #3"))
.build());
col.add(ProgressBar.builder()
.value(0.75f)
.labelKey("monmod.bar.dispo")
.showPercent(false)
.build());
col.add(Button.builder()
.text("monmod.btn.acheter")
.command("shop buy " + articleSelectionne[0].toLowerCase() + " 1")
.borderColor(ApocalyColors.SUCCESS)
.build());
col.add(Button.builder()
.text("monmod.btn.vendre")
.command("shop sell " + articleSelectionne[0].toLowerCase() + " 1")
.build());
}
// ── Panneau droit : inventaire ────────────────────────────────────────────
private void buildInventairePanel(Column col) {
col.add(Label.builder()
.text("monmod.label.solde")
.value(DataSource.of("1 500 ⬡"))
.accent()
.build());
col.add(Label.builder()
.text("monmod.label.contenu")
.title()
.build());
col.add(Row.create()
.add(ItemIcon.builder().item(Items.DIAMOND).showCount(false).build())
.add(ItemIcon.builder().item(Items.EMERALD).showCount(false).build())
.add(ItemIcon.builder()
.item(new ItemStack(Items.GOLD_INGOT, 3))
.showCount(true)
.build()));
col.add(Label.builder()
.text("monmod.label.transactions")
.title()
.build());
col.add(KeyValueRow.builder()
.key("monmod.row.achats")
.value(DataSource.of("7"))
.build());
col.add(KeyValueRow.builder()
.key("monmod.row.ventes")
.value(DataSource.of("3"))
.build());
}
}
Ouvrir l'écran
Minecraft.getInstance().execute(() ->
Minecraft.getInstance().setScreen(new MarchandScreen())
);
Clés i18n requises
{
"monmod.screen.marchand": "Marché d'Apocaly",
"monmod.panel.boutique": "Boutique",
"monmod.panel.details": "Détails",
"monmod.panel.inventaire": "Mon inventaire",
"monmod.search.articles": "Rechercher…",
"monmod.label.article": "Article : %s",
"monmod.row.prix": "Prix",
"monmod.row.stock": "Stock",
"monmod.row.vendeur": "Vendeur",
"monmod.bar.dispo": "Disponibilité",
"monmod.btn.acheter": "Acheter",
"monmod.btn.vendre": "Vendre",
"monmod.label.solde": "Solde : %s",
"monmod.label.contenu": "Mes items",
"monmod.label.transactions": "Transactions",
"monmod.row.achats": "Achats",
"monmod.row.ventes": "Ventes"
}
Points clés
| Point | Explication |
|---|---|
État partagé via String[] | Les lambdas de buildPanels() capturent l'environnement. Utiliser un tableau 1-élément (String[]) pour contourner la contrainte effectively final. En production, préfère des champs d'instance. |
init() au clic de sélection | Reconstruit les 3 panneaux pour refléter la nouvelle sélection. La recherche est perdue — considère update() si tu veux la préserver. |
| Largeurs différentes | Chaque panneau a sa propre largeur — adapte selon le contenu. |