Skip to content

Commit

Permalink
feat: Skill point loadouts smooth scrolling (#2348)
Browse files Browse the repository at this point in the history
* comments for clarity

* implement scrolling

* remove print

* faster scrolling, del to delete loadouts

* fix missing commit

* draggable scrollbar

* shorten explains, remove name check

* clarify 56/57 comment

* separate scroll logic

* Update common/src/main/java/com/wynntils/screens/skillpointloadouts/widgets/DeleteButton.java

---------

Co-authored-by: Kristof Kovacs <49001742+kristofbolyai@users.noreply.github.com>
  • Loading branch information
DonkeyBlaster and kristofbolyai committed Mar 20, 2024
1 parent 21292fd commit 04395e5
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 141 deletions.
Expand Up @@ -17,6 +17,7 @@
import com.wynntils.screens.skillpointloadouts.widgets.LoadButton;
import com.wynntils.screens.skillpointloadouts.widgets.LoadoutWidget;
import com.wynntils.screens.skillpointloadouts.widgets.SaveButton;
import com.wynntils.screens.skillpointloadouts.widgets.ScrollBar;
import com.wynntils.utils.colors.CommonColors;
import com.wynntils.utils.render.FontRenderer;
import com.wynntils.utils.render.RenderUtils;
Expand All @@ -29,17 +30,22 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.resources.language.I18n;
import net.minecraft.network.chat.Component;
import org.lwjgl.glfw.GLFW;

public final class SkillPointLoadoutsScreen extends WynntilsGridLayoutScreen {
private static final int MAX_LOADOUTS_PER_PAGE = 11;
private List<LoadoutWidget> loadoutWidgets = new ArrayList<>();

private boolean firstInit = true;
private final List<Pair<Supplier<String>, Function<Skill, Integer>>> summaryParts = new ArrayList<>();

private SaveButton saveAssignedButton;
private SaveButton saveBuildButton;
Expand All @@ -51,6 +57,9 @@ public final class SkillPointLoadoutsScreen extends WynntilsGridLayoutScreen {
private WynntilsButton deleteButton;
private WynntilsButton convertButton;

private ScrollBar scrollBar;
private float scrollPercent = 0;

private SkillPointLoadoutsScreen() {
super(Component.literal("Skill Point Loadouts Screen"));
}
Expand All @@ -64,11 +73,36 @@ protected void doInit() {
super.doInit();
if (firstInit) {
firstInit = false;

summaryParts.add(Pair.of(
() -> (Models.SkillPoint.hasIllegalAssigned() ? ChatFormatting.RED : "")
+ I18n.get(
"screens.wynntils.skillPointLoadouts.assigned", Models.SkillPoint.getAssignedSum()),
Models.SkillPoint::getAssignedSkillPoints));
summaryParts.add(Pair.of(
() -> I18n.get("screens.wynntils.skillPointLoadouts.gear", Models.SkillPoint.getGearSum()),
Models.SkillPoint::getGearSkillPoints));
summaryParts.add(Pair.of(
() -> I18n.get("screens.wynntils.skillPointLoadouts.tomes", Models.SkillPoint.getTomeSum()),
Models.SkillPoint::getTomeSkillPoints));
summaryParts.add(Pair.of(
() -> I18n.get("screens.wynntils.skillPointLoadouts.crafted", Models.SkillPoint.getCraftedSum()),
Models.SkillPoint::getCraftedSkillPoints));
summaryParts.add(Pair.of(
() -> I18n.get(
"screens.wynntils.skillPointLoadouts.statusEffects",
Models.SkillPoint.getStatusEffectsSum()),
Models.SkillPoint::getStatusEffectSkillPoints));
summaryParts.add(Pair.of(
() -> I18n.get("screens.wynntils.skillPointLoadouts.total", Models.SkillPoint.getTotalSum()),
Models.SkillPoint::getTotalSkillPoints));

Models.SkillPoint.populateSkillPoints();
}

populateLoadouts();

// region Widget initialization
saveNameInput = new TextInputBoxWidget(
(int) (dividedWidth * 35),
(int) (dividedHeight * 24),
Expand Down Expand Up @@ -131,6 +165,10 @@ protected void doInit() {
Component.translatable("screens.wynntils.skillPointLoadouts.convert"),
this);
this.addRenderableWidget(convertButton);
// endregion

scrollBar = new ScrollBar(dividedWidth * 30, dividedHeight * 8, dividedWidth * 0.5f, 0, this, dividedHeight);
this.addRenderableWidget(scrollBar);

setSelectedLoadout(null);
}
Expand Down Expand Up @@ -205,152 +243,33 @@ public void doRender(GuiGraphics guiGraphics, int mouseX, int mouseY, float part
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString((Models.SkillPoint.hasIllegalAssigned() ? ChatFormatting.RED : "")
+ I18n.get(
"screens.wynntils.skillPointLoadouts.assigned",
Models.SkillPoint.getAssignedSum())),
dividedWidth * 35,
dividedHeight * 11,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getAssignedSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 11,
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(
I18n.get("screens.wynntils.skillPointLoadouts.gear", Models.SkillPoint.getGearSum())),
dividedWidth * 35,
dividedHeight * 13,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getGearSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 13,
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(
I18n.get("screens.wynntils.skillPointLoadouts.tomes", Models.SkillPoint.getTomeSum())),
dividedWidth * 35,
dividedHeight * 15,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getTomeSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 15,
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(I18n.get(
"screens.wynntils.skillPointLoadouts.crafted", Models.SkillPoint.getCraftedSum())),
dividedWidth * 35,
dividedHeight * 17,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getCraftedSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 17,
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(I18n.get(
"screens.wynntils.skillPointLoadouts.statusEffects",
Models.SkillPoint.getStatusEffectsSum())),
dividedWidth * 35,
dividedHeight * 19,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getStatusEffectSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 19,
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(
I18n.get("screens.wynntils.skillPointLoadouts.total", Models.SkillPoint.getTotalSum())),
dividedWidth * 35,
dividedHeight * 21,
CommonColors.WHITE,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int i = 0; i < 5; i++) {

// i iterates over the summary parts populating top to bottom
// j iterates over the skills populating left to right
for (int i = 0; i < summaryParts.size(); i++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[i].getColorCode() + ""
+ Models.SkillPoint.getTotalSkillPoints(Skill.values()[i])),
dividedWidth * (51 + i * 2),
dividedHeight * 21,
StyledText.fromString(summaryParts.get(i).key().get()),
dividedWidth * 35,
dividedHeight * (11 + i * 2),
CommonColors.WHITE,
HorizontalAlignment.CENTER,
HorizontalAlignment.LEFT,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
for (int j = 0; j < 5; j++) {
FontRenderer.getInstance()
.renderText(
poseStack,
StyledText.fromString(Skill.values()[j].getColorCode() + ""
+ summaryParts.get(i).value().apply(Skill.values()[j])),
dividedWidth * (51 + j * 2),
dividedHeight * (10 + i * 2),
CommonColors.WHITE,
HorizontalAlignment.CENTER,
VerticalAlignment.BOTTOM,
TextShadow.NORMAL);
}
}

if (hasSaveNameConflict) {
Expand Down Expand Up @@ -495,7 +414,29 @@ public void doRender(GuiGraphics guiGraphics, int mouseX, int mouseY, float part
}
// endregion

// region Scrollbar
if (loadoutWidgets.size() > MAX_LOADOUTS_PER_PAGE) {
scrollBar.visible = true;
scrollBar.active = true;
float visibleRatio = Math.min(1, (float) MAX_LOADOUTS_PER_PAGE / loadoutWidgets.size());
float scrollbarLength = dividedHeight * 48 * visibleRatio + 1;
scrollBar.setY((int) (dividedHeight * 8 + dividedHeight * 48 * scrollPercent));
scrollBar.setHeight((int) scrollbarLength);
} else {
scrollBar.visible = false;
scrollBar.active = false;
}
// Only render from 8 to 56 for scrollable area
// -/+ 1 to not overlap/cut off content
RenderUtils.createRectMask(
poseStack,
(int) (dividedWidth * 4) - 1,
(int) (dividedHeight * 8) + 1,
(int) (dividedWidth * 26) + 1,
(int) (dividedHeight * 48) + 1);
loadoutWidgets.forEach(widget -> widget.render(guiGraphics, mouseX, mouseY, partialTick));
RenderUtils.clearMask();
// endregion
}

@Override
Expand All @@ -509,6 +450,50 @@ public boolean doMouseClicked(double mouseX, double mouseY, int button) {
return super.doMouseClicked(mouseX, mouseY, button);
}

// baseYPosition - dividedHeight * maxScrollOffset * scrollPercent = dividedHeight * 52
// Solve for maxScrollOffset
// Full explanation in #artemis-dev
@Override
public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
// In terms of grid divisions
// 11 loadouts are fully displayed from 9 to 56
// 12th one is very slightly cut off (it needs 57)
if (loadoutWidgets.size() <= MAX_LOADOUTS_PER_PAGE) {
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}
doScroll(scrollY);
return super.mouseScrolled(mouseX, mouseY, scrollX, scrollY);
}

public void doScroll(double scrollAmount) {
int scrollableWidgets = Math.max(0, loadoutWidgets.size() - MAX_LOADOUTS_PER_PAGE);
float scrollableRatio = (float) scrollableWidgets / loadoutWidgets.size();
float maxScrollOffset = (4 * (loadoutWidgets.size() - 1) - 43) / scrollableRatio;
scrollPercent = (float) Math.max(0, Math.min(scrollableRatio, scrollPercent - scrollAmount / 50));

loadoutWidgets.forEach(widget -> {
float baseYPosition = dividedHeight * (9f + loadoutWidgets.indexOf(widget) * 4f);

float scrollOffset = dividedHeight * maxScrollOffset * scrollPercent;
widget.setY((int) (baseYPosition - scrollOffset));
widget.visible = !(widget.getY() <= dividedHeight * 4) && !(widget.getY() >= dividedHeight * 56);
});
}

@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if (keyCode == GLFW.GLFW_KEY_DELETE && deleteButton.active) {
deleteButton.onPress();
return true;
} else if (keyCode == GLFW.GLFW_KEY_END) {
doScroll(Float.NEGATIVE_INFINITY);
} else if (keyCode == GLFW.GLFW_KEY_HOME) {
doScroll(Float.POSITIVE_INFINITY);
}

return super.keyPressed(keyCode, scanCode, modifiers);
}

public void setSelectedLoadout(Pair<String, SavableSkillPointSet> loadout) {
if (loadout == null) {
selectedLoadout = null;
Expand Down
Expand Up @@ -22,5 +22,6 @@ public void onPress() {
Models.SkillPoint.deleteLoadout(parent.selectedLoadout.key());
parent.setSelectedLoadout(null);
parent.populateLoadouts();
parent.doScroll(0); // force scrollPercent refresh
}
}

0 comments on commit 04395e5

Please sign in to comment.