aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2025-07-31 16:04:27 +0200
committerLinnea Gräf <nea@nea.moe>2025-07-31 18:09:40 +0200
commit93dfb15f25dcbe857e99d1a1fcbbc2205f06eef6 (patch)
tree6604eb393b12d02d6dc3e381002a648c9f854178
parenta893415fec63410c16bb45924989c6b51e5f85ed (diff)
downloadSkyblocker-93dfb15f25dcbe857e99d1a1fcbbc2205f06eef6.tar.gz
Skyblocker-93dfb15f25dcbe857e99d1a1fcbbc2205f06eef6.tar.bz2
Skyblocker-93dfb15f25dcbe857e99d1a1fcbbc2205f06eef6.zip
feat: new profile viewer scaffolding
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java95
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java10
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ErrorPage.java34
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/LoadingPage.java31
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileLoadState.java20
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerPage.java27
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerScreenRework.java139
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerWidget.java19
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/TextWidget.java31
-rw-r--r--src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/pages/MainPage.java40
10 files changed, 396 insertions, 50 deletions
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
index 8da17988..d14b47cc 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerNavButton.java
@@ -11,51 +11,56 @@ import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import java.util.Map;
+import java.util.function.Consumer;
public class ProfileViewerNavButton extends ClickableWidget {
- private static final Identifier BUTTON_TEXTURES_TOGGLED = Identifier.of("container/creative_inventory/tab_top_selected_2");
- private static final Identifier BUTTON_TEXTURES = Identifier.of("container/creative_inventory/tab_top_unselected_2");
- private boolean toggled;
- private final int index;
- private final ProfileViewerScreen screen;
- private final ItemStack icon;
-
- private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries(
- Map.entry("Skills", Ico.IRON_SWORD),
- Map.entry("Slayers", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")),
- Map.entry("Pets", Ico.BONE),
- Map.entry("Dungeons", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
- Map.entry("Inventories", Ico.E_CHEST),
- Map.entry("Collections", Ico.PAINTING)
- );
-
- public ProfileViewerNavButton(ProfileViewerScreen screen, String tabName, int index, boolean toggled) {
- super(-100, -100, 28, 32, Text.empty());
- this.screen = screen;
- this.toggled = toggled;
- this.index = index;
- this.icon = HEAD_ICON.getOrDefault(tabName, Ico.BARRIER);
- }
-
- @Override
- protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
- context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, toggled ? BUTTON_TEXTURES_TOGGLED : BUTTON_TEXTURES, this.getX(), this.getY(), this.width, this.height - ((this.toggled) ? 0 : 4));
- context.drawItem(this.icon, this.getX() + 6, this.getY() + (this.toggled ? 7 : 9));
- }
-
- @Override
- public void onClick(double mouseX, double mouseY) {
- screen.onNavButtonClick(this);
- }
-
- @Override
- protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
-
- public void setToggled(boolean toggled) {
- this.toggled = toggled;
- }
-
- public int getIndex() {
- return index;
- }
+ private static final Identifier BUTTON_TEXTURES_TOGGLED = Identifier.of("container/creative_inventory/tab_top_selected_2");
+ private static final Identifier BUTTON_TEXTURES = Identifier.of("container/creative_inventory/tab_top_unselected_2");
+ private boolean toggled;
+ private final int index;
+ private final Consumer<ProfileViewerNavButton> onClick;
+ private final ItemStack icon;
+
+ private static final Map<String, ItemStack> HEAD_ICON = Map.ofEntries(
+ Map.entry("Skills", Ico.IRON_SWORD),
+ Map.entry("Slayers", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzkzMzZkN2NjOTVjYmY2Njg5ZjVlOGM5NTQyOTRlYzhkMWVmYzQ5NGE0MDMxMzI1YmI0MjdiYzgxZDU2YTQ4NGQifX19")),
+ Map.entry("Pets", Ico.BONE),
+ Map.entry("Dungeons", ProfileViewerUtils.createSkull("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHBzOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzliNTY4OTViOTY1OTg5NmFkNjQ3ZjU4NTk5MjM4YWY1MzJkNDZkYjljMWIwMzg5YjhiYmViNzA5OTlkYWIzM2QifX19")),
+ Map.entry("Inventories", Ico.E_CHEST),
+ Map.entry("Collections", Ico.PAINTING)
+ );
+
+ public ProfileViewerNavButton(Consumer<ProfileViewerNavButton> onClick, String tabName, ItemStack icon, int index, boolean toggled) {
+ super(-100, -100, 28, 32, Text.empty());
+ this.onClick = onClick;
+ this.toggled = toggled;
+ this.index = index;
+ this.icon = icon;
+ }
+
+ public ProfileViewerNavButton(ProfileViewerScreen screen, String tabName, int index, boolean toggled) {
+ this(screen::onNavButtonClick, tabName, HEAD_ICON.getOrDefault(tabName, Ico.BARRIER), index, toggled);
+ }
+
+ @Override
+ protected void renderWidget(DrawContext context, int mouseX, int mouseY, float delta) {
+ context.drawGuiTexture(RenderPipelines.GUI_TEXTURED, toggled ? BUTTON_TEXTURES_TOGGLED : BUTTON_TEXTURES, this.getX(), this.getY(), this.width, this.height - ((this.toggled) ? 0 : 4));
+ context.drawItem(this.icon, this.getX() + 6, this.getY() + (this.toggled ? 7 : 9));
+ }
+
+ @Override
+ public void onClick(double mouseX, double mouseY) {
+ onClick.accept(this);
+ }
+
+ @Override
+ protected void appendClickableNarrations(NarrationMessageBuilder builder) {}
+
+ public void setToggled(boolean toggled) {
+ this.toggled = toggled;
+ }
+
+ public int getIndex() {
+ return index;
+ }
}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
index 907b3cc6..9f2f69bc 100644
--- a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/ProfileViewerScreen.java
@@ -1,10 +1,9 @@
package de.hysky.skyblocker.skyblock.profileviewer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonParser;
+import com.google.gson.*;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
+import com.mojang.util.UUIDTypeAdapter;
import com.mojang.util.UndashedUuid;
import de.hysky.skyblocker.SkyblockerMod;
@@ -12,6 +11,7 @@ import de.hysky.skyblocker.annotations.Init;
import de.hysky.skyblocker.skyblock.profileviewer.collections.CollectionsPage;
import de.hysky.skyblocker.skyblock.profileviewer.dungeons.DungeonsPage;
import de.hysky.skyblocker.skyblock.profileviewer.inventory.InventoryPage;
+import de.hysky.skyblocker.skyblock.profileviewer.rework.ProfileViewerScreenRework;
import de.hysky.skyblocker.skyblock.profileviewer.skills.SkillsPage;
import de.hysky.skyblocker.skyblock.profileviewer.slayers.SlayersPage;
import de.hysky.skyblocker.utils.ApiAuthentication;
@@ -226,9 +226,9 @@ public class ProfileViewerScreen extends Screen {
LiteralArgumentBuilder<FabricClientCommandSource> literalArgumentBuilder = ClientCommandManager.literal("pv")
.then(ClientCommandManager.argument("username", StringArgumentType.string())
.suggests((source, builder) -> CommandSource.suggestMatching(getPlayerSuggestions(source.getSource()), builder))
- .executes(Scheduler.queueOpenScreenFactoryCommand(context -> new ProfileViewerScreen(StringArgumentType.getString(context, "username"))))
+ .executes(Scheduler.queueOpenScreenFactoryCommand(context -> ProfileViewerScreenRework.forPlayer(StringArgumentType.getString(context, "username"))))
)
- .executes(Scheduler.queueOpenScreenCommand(() -> new ProfileViewerScreen(MinecraftClient.getInstance().getSession().getUsername())));
+ .executes(Scheduler.queueOpenScreenCommand(() -> ProfileViewerScreenRework.forPlayer(MinecraftClient.getInstance().getSession().getUsername())));
dispatcher.register(literalArgumentBuilder);
dispatcher.register(ClientCommandManager.literal(SkyblockerMod.NAMESPACE).then(literalArgumentBuilder));
});
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ErrorPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ErrorPage.java
new file mode 100644
index 00000000..934a4a4e
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ErrorPage.java
@@ -0,0 +1,34 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+public record ErrorPage(ProfileLoadState.Error error) implements ProfileViewerPage {
+
+ @Override
+ public int getSortIndex() {
+ return 0;
+ }
+
+ @Override
+ public @NotNull ItemStack getIcon() {
+ return Ico.BARRIER;
+ }
+
+ @Override
+ public @NotNull String getName() {
+ return "Error Loading";
+ }
+
+ @Override
+ public @NotNull List<ProfileViewerWidget.Instance> getWidgets() {
+ return List.of(
+ ProfileViewerWidget.widget(ProfileViewerScreenRework.GUI_WIDTH / 2, 8, TextWidget.centered(Text.of("Error!"))),
+ ProfileViewerWidget.widget(ProfileViewerScreenRework.GUI_WIDTH / 2, 19, TextWidget.centered(Text.of(error.message())))
+ );
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/LoadingPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/LoadingPage.java
new file mode 100644
index 00000000..d2b523b4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/LoadingPage.java
@@ -0,0 +1,31 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+
+import java.util.List;
+
+public class LoadingPage implements ProfileViewerPage {
+ @Override
+ public int getSortIndex() {
+ return 0;
+ }
+
+ @Override
+ public ItemStack getIcon() {
+ return Ico.CLOCK;
+ }
+
+ @Override
+ public String getName() {
+ return "Loading...";
+ }
+
+ @Override
+ public List<ProfileViewerWidget.Instance> getWidgets() {
+ return List.of(
+ ProfileViewerWidget.widget(ProfileViewerScreenRework.GUI_WIDTH / 2, 8, TextWidget.centered(Text.of("Loading profile...")))
+ );
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileLoadState.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileLoadState.java
new file mode 100644
index 00000000..ac8f5bff
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileLoadState.java
@@ -0,0 +1,20 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import de.hysky.skyblocker.skyblock.profileviewer.model.ApiProfile;
+import de.hysky.skyblocker.skyblock.profileviewer.model.ProfileMember;
+
+import java.util.UUID;
+
+public sealed interface ProfileLoadState {
+ record SuccessfulLoad(
+ ApiProfile profile,
+ UUID mainMemberId,
+ ProfileMember member
+ ) implements ProfileLoadState {}
+
+ record Error(
+ String message
+ ) implements ProfileLoadState {}
+
+ record Loading() implements ProfileLoadState {}
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerPage.java
new file mode 100644
index 00000000..184f6660
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerPage.java
@@ -0,0 +1,27 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import net.minecraft.item.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.NotNullByDefault;
+
+import java.util.List;
+
+@NotNullByDefault
+public interface ProfileViewerPage extends Comparable<ProfileViewerPage> {
+ int getSortIndex();
+
+ @Override
+ default int compareTo(@NotNull ProfileViewerPage o) {
+ return Integer.compare(this.getSortIndex(), o.getSortIndex());
+ }
+
+ static ProfileViewerWidget.Instance widget(int x, int y, ProfileViewerWidget widget) {
+ return ProfileViewerWidget.widget(x, y, widget);
+ }
+
+ ItemStack getIcon();
+
+ String getName();
+
+ List<ProfileViewerWidget.Instance> getWidgets();
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerScreenRework.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerScreenRework.java
new file mode 100644
index 00000000..4158fcf4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerScreenRework.java
@@ -0,0 +1,139 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.mojang.util.UUIDTypeAdapter;
+import com.mojang.util.UndashedUuid;
+import de.hysky.skyblocker.SkyblockerMod;
+import de.hysky.skyblocker.skyblock.profileviewer.ProfileViewerNavButton;
+import de.hysky.skyblocker.skyblock.profileviewer.model.ApiProfileResponse;
+import de.hysky.skyblocker.utils.ApiUtils;
+import de.hysky.skyblocker.utils.ProfileUtils;
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.gl.RenderPipelines;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.client.gui.screen.Screen;
+import net.minecraft.text.Text;
+import net.minecraft.util.Identifier;
+
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+public class ProfileViewerScreenRework extends Screen {
+ public static final Gson GSON = new GsonBuilder()
+ .registerTypeAdapter(UUID.class, new UUIDTypeAdapter())
+ .create();
+ public static final List<Function<ProfileLoadState.SuccessfulLoad, ProfileViewerPage>> PAGE_CONSTRUCTORS =
+ new ArrayList<>();
+
+ public ProfileViewerScreenRework() {
+ super(Text.of("SkyBlocker Profile Viewer"));
+ displayLoadedProfile(new ProfileLoadState.Loading());
+ }
+
+ public static Screen forPlayer(String username) {
+ var screen = new ProfileViewerScreenRework();
+ screen.loadProfilesFromPlayer(username);
+ return screen;
+ }
+
+ //<editor-fold desc="Loading and state management">
+ private CompletableFuture<ProfileLoadState> reload;
+ private ProfileLoadState currentLoadState;
+ private List<ProfileViewerPage> pages;
+ private List<ProfileViewerNavButton> buttons;
+ private List<ProfileViewerWidget.Instance> widgets;
+ private int selectedIndex = 0;
+
+
+ public ProfileLoadState getCurrentLoadState() {
+ return currentLoadState;
+ }
+
+ public void displayLoadedProfile(ProfileLoadState profileLoadState) {
+ this.currentLoadState = profileLoadState;
+ this.pages = switch (profileLoadState) {
+ case ProfileLoadState.Error error -> List.of(new ErrorPage(error));
+ case ProfileLoadState.SuccessfulLoad successfulLoad ->
+ PAGE_CONSTRUCTORS.stream().sorted().map(it -> it.apply(successfulLoad)).toList();
+ case ProfileLoadState.Loading ignored -> List.of(new LoadingPage());
+ };
+ this.buttons = new ArrayList<>();
+ for (int i = 0; i < pages.size(); i++) {
+ var page = pages.get(i);
+ buttons.add(new ProfileViewerNavButton(ignored -> setSelectedPage(selectedIndex), page.getName(), page.getIcon(), i, false));
+ }
+ setSelectedPage(0);
+ }
+
+ public int getSelectedIndex() {
+ return selectedIndex;
+ }
+
+ public ProfileViewerPage getSelectedPage() {
+ return pages.get(selectedIndex);
+ }
+
+ public void setSelectedPage(int index) {
+ this.selectedIndex = index;
+ for (int i = 0; i < buttons.size(); i++) {
+ buttons.get(i).setToggled(i == selectedIndex);
+ }
+ widgets = pages.get(selectedIndex).getWidgets();
+ }
+
+ public CompletableFuture<ProfileLoadState> loadProfilesFromPlayer(String name) {
+ if (reload != null) {
+ reload.cancel(true);
+ }
+ this.displayLoadedProfile(new ProfileLoadState.Loading());
+ return reload = ProfileUtils.fetchFullProfile(name)
+ .thenApplyAsync(jsonObject -> GSON.fromJson(jsonObject, ApiProfileResponse.class))
+ .thenApplyAsync(apiProfileResponse -> apiProfileResponse
+ .profiles
+ .stream()
+ .max(Comparator.comparing(it -> it.selected))
+ .<ProfileLoadState>map(selectedProfile -> {
+ var uuid = UndashedUuid.fromStringLenient(ApiUtils.name2Uuid(name));
+ return new ProfileLoadState.SuccessfulLoad(
+ selectedProfile,
+ uuid,
+ selectedProfile.members.get(uuid)
+ );
+ })
+ .orElseGet(() -> new ProfileLoadState.Error("No profile found")))
+ .exceptionally(ex -> new ProfileLoadState.Error(ex.getMessage()))
+ .thenApplyAsync(load -> {
+ displayLoadedProfile(load);
+ return load;
+ }, MinecraftClient.getInstance());
+ }
+ //</editor-fold>
+
+ private static final Identifier TEXTURE = Identifier.of(SkyblockerMod.NAMESPACE, "textures/gui/profile_viewer/base_plate.png");
+ public static final int GUI_WIDTH = 322;
+ public static final int GUI_HEIGHT = 180;
+
+ @Override
+ public void render(DrawContext context, int mouseX, int mouseY, float deltaTicks) {
+ super.render(context, mouseX, mouseY, deltaTicks);
+ int rootX = width / 2 - GUI_WIDTH / 2;
+ int rootY = height / 2 - GUI_HEIGHT / 2 + 5;
+
+ context.drawTexture(RenderPipelines.GUI_TEXTURED, TEXTURE, rootX, rootY, 0, 0, GUI_WIDTH, GUI_HEIGHT, GUI_WIDTH, GUI_HEIGHT);
+ for (var button : buttons) {
+ button.setX(rootX + button.getIndex() * 28 + 4);
+ button.setY(rootY - 28);
+ button.render(context, mouseX, mouseY, deltaTicks);
+ }
+
+ for (var widget : widgets) {
+ widget.render(context, rootX, rootY, mouseX, mouseY, deltaTicks);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerWidget.java
new file mode 100644
index 00000000..d601b6e4
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/ProfileViewerWidget.java
@@ -0,0 +1,19 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import net.minecraft.client.gui.DrawContext;
+
+public interface ProfileViewerWidget {
+ void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY, float deltaTicks);
+
+ default void click(int x, int y, int mouseX, int mouseY) {}
+
+ static Instance widget(int x, int y, ProfileViewerWidget widget) {
+ return new Instance(widget, x, y);
+ }
+
+ record Instance(ProfileViewerWidget widget, int xRelative, int yRelative) {
+ void render(DrawContext drawContext, int rootX, int rootY, int mouseX, int mouseY, float deltaTicks) {
+ widget.render(drawContext, rootX + xRelative, rootY + yRelative, mouseX, mouseY, deltaTicks);
+ }
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/TextWidget.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/TextWidget.java
new file mode 100644
index 00000000..a4e515d2
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/TextWidget.java
@@ -0,0 +1,31 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework;
+
+import net.minecraft.client.MinecraftClient;
+import net.minecraft.client.font.TextRenderer;
+import net.minecraft.client.gui.DrawContext;
+import net.minecraft.text.Text;
+
+public final class TextWidget implements ProfileViewerWidget {
+ private final TextRenderer textRenderer;
+ private final Text text;
+ private final int offset;
+
+ public TextWidget(TextRenderer textRenderer, Text text, boolean centered) {
+ this.textRenderer = textRenderer;
+ this.text = text;
+ this.offset = centered ? -textRenderer.getWidth(text) / 2 : 0;
+ }
+
+ public static TextWidget leftAligned(Text text) {
+ return new TextWidget(MinecraftClient.getInstance().textRenderer, text, false);
+ }
+
+ public static TextWidget centered(Text text) {
+ return new TextWidget(MinecraftClient.getInstance().textRenderer, text, true);
+ }
+
+ @Override
+ public void render(DrawContext drawContext, int x, int y, int mouseX, int mouseY, float deltaTicks) {
+ drawContext.drawText(textRenderer, text, x + offset, y, -1, true);
+ }
+}
diff --git a/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/pages/MainPage.java b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/pages/MainPage.java
new file mode 100644
index 00000000..5387373c
--- /dev/null
+++ b/src/main/java/de/hysky/skyblocker/skyblock/profileviewer/rework/pages/MainPage.java
@@ -0,0 +1,40 @@
+package de.hysky.skyblocker.skyblock.profileviewer.rework.pages;
+
+import de.hysky.skyblocker.annotations.Init;
+import de.hysky.skyblocker.skyblock.profileviewer.rework.*;
+import de.hysky.skyblocker.skyblock.tabhud.util.Ico;
+import net.minecraft.item.ItemStack;
+import net.minecraft.text.Text;
+
+import java.util.List;
+
+public class MainPage implements ProfileViewerPage {
+ public MainPage(ProfileLoadState.SuccessfulLoad load) {}
+
+ @Init
+ public static void init() {
+ ProfileViewerScreenRework.PAGE_CONSTRUCTORS.add(MainPage::new);
+ }
+
+ @Override
+ public int getSortIndex() {
+ return 0;
+ }
+
+ @Override
+ public ItemStack getIcon() {
+ return Ico.IRON_SWORD;
+ }
+
+ @Override
+ public String getName() {
+ return "Skills";
+ }
+
+ @Override
+ public List<ProfileViewerWidget.Instance> getWidgets() {
+ return List.of(
+ ProfileViewerWidget.widget(ProfileViewerScreenRework.GUI_WIDTH / 2, 8, TextWidget.centered(Text.of("Skills")))
+ );
+ }
+}