-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds a GUI which allows jumping freecam to a player within render distance. Could be expanded in future with tripod locations and other "saved" positions.
- Loading branch information
1 parent
7ba7f2a
commit 27196d2
Showing
14 changed files
with
580 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
234 changes: 234 additions & 0 deletions
234
common/src/main/java/net/xolt/freecam/gui/go/GotoScreen.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
package net.xolt.freecam.gui.go; | ||
|
||
import me.shedaniel.autoconfig.AutoConfig; | ||
import net.minecraft.ChatFormatting; | ||
import net.minecraft.client.gui.GuiGraphics; | ||
import net.minecraft.client.gui.components.Button; | ||
import net.minecraft.client.gui.components.CycleButton; | ||
import net.minecraft.client.gui.components.EditBox; | ||
import net.minecraft.client.gui.components.Tooltip; | ||
import net.minecraft.client.gui.layouts.FrameLayout; | ||
import net.minecraft.client.gui.layouts.LinearLayout; | ||
import net.minecraft.client.gui.navigation.CommonInputs; | ||
import net.minecraft.client.gui.screens.Screen; | ||
import net.minecraft.network.chat.CommonComponents; | ||
import net.minecraft.network.chat.Component; | ||
import net.minecraft.resources.ResourceLocation; | ||
import net.minecraft.util.FastColor; | ||
import net.xolt.freecam.Freecam; | ||
import net.xolt.freecam.config.ModConfig; | ||
import net.xolt.freecam.gui.Texture; | ||
import net.xolt.freecam.util.FreeCamera; | ||
import org.lwjgl.glfw.GLFW; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
import static net.xolt.freecam.config.ModBindings.KEY_GOTO_GUI; | ||
|
||
public class GotoScreen extends Screen { | ||
public static final int GRAY_COLOR = FastColor.ARGB32.color(255, 74, 74, 74); | ||
public static final int WHITE_COLOR = FastColor.ARGB32.color(255, 255, 255, 255); | ||
private static final int GUI_WIDTH = 236; | ||
private static final int GUI_TOP = 50; | ||
private static final int LIST_TOP = GUI_TOP + 8; | ||
private static final int LIST_ITEM_HEIGHT = 36; | ||
private static final ResourceLocation SEARCH_ICON_TEXTURE = new ResourceLocation("icon/search"); | ||
private static final Component SEARCH_TEXT = Component.translatable("gui.recipebook.search_hint").withStyle(ChatFormatting.ITALIC).withStyle(ChatFormatting.GRAY); | ||
private static final Texture JUMP_BACKGROUND = new Texture(new ResourceLocation(Freecam.MOD_ID, "textures/gui/goto_background.png")); | ||
private static final Texture JUMP_LIST_BACKGROUND = new Texture(new ResourceLocation(Freecam.MOD_ID, "textures/gui/goto_list_background.png")); | ||
|
||
private ListWidget list; | ||
private boolean initialized; | ||
private Button buttonBack; | ||
private Button buttonJump; | ||
private CycleButton<ModConfig.Perspective> buttonPerspective; | ||
private EditBox searchBox; | ||
private String currentSearch; | ||
|
||
public GotoScreen() { | ||
super(Component.translatable("gui.freecam.goto.title")); | ||
} | ||
|
||
@Override | ||
protected void init() { | ||
super.init(); | ||
|
||
if (!this.initialized) { | ||
this.list = new ListWidget(this, this.minecraft, 0, this.height, LIST_ITEM_HEIGHT); | ||
this.searchBox = new EditBox(this.font, 0, 15, SEARCH_TEXT); | ||
this.searchBox.setHint(SEARCH_TEXT); | ||
this.searchBox.setMaxLength(16); | ||
this.searchBox.setVisible(true); | ||
this.searchBox.setTextColor(0xFFFFFF); | ||
this.searchBox.setResponder(this::onSearchChange); | ||
|
||
this.buttonJump = Button.builder(Component.translatable("gui.freecam.goto.button.go"), button -> this.go()) | ||
.tooltip(Tooltip.create(Component.translatable("gui.freecam.goto.button.go.@Tooltip"))) | ||
.width(48) | ||
.build(); | ||
|
||
this.buttonPerspective = CycleButton | ||
.builder((ModConfig.Perspective value) -> Component.translatable(value.getKey())) | ||
.withValues(ModConfig.Perspective.values()) | ||
.withInitialValue(ModConfig.INSTANCE.hidden.gotoPlayerPerspective) | ||
.withTooltip(value -> Tooltip.create(Component.translatable("gui.freecam.goto.button.perspective.@Tooltip"))) | ||
.displayOnlyValue() | ||
.create(0, 0, 80, 20, null, (button, value) -> { | ||
ModConfig.INSTANCE.hidden.gotoPlayerPerspective = value; | ||
AutoConfig.getConfigHolder(ModConfig.class).save(); | ||
}); | ||
|
||
this.buttonBack = Button.builder(CommonComponents.GUI_BACK, button -> this.onClose()).width(48).build(); | ||
} | ||
|
||
int listTop = LIST_TOP + 16; | ||
int listBottom = this.getListBottom(); | ||
int innerWidth = GUI_WIDTH - 10; | ||
int innerX = (this.width - innerWidth) / 2; | ||
|
||
this.list.setY(listTop); | ||
this.list.setSize(this.width, listBottom - listTop); | ||
this.searchBox.setPosition(innerX + 20, LIST_TOP + 1); | ||
this.searchBox.setWidth(this.list.getRowWidth() - 19); | ||
|
||
FrameLayout positioner = new FrameLayout(innerX, listBottom + 3, innerWidth, 0); | ||
positioner.defaultChildLayoutSetting() | ||
.alignVerticallyBottom() | ||
.alignHorizontallyRight(); | ||
LinearLayout layout = positioner.addChild(LinearLayout.horizontal()); | ||
layout.defaultCellSetting() | ||
.alignVerticallyBottom() | ||
.paddingHorizontal(2); | ||
|
||
layout.addChild(this.buttonBack); | ||
layout.addChild(this.buttonPerspective); | ||
layout.addChild(this.buttonJump); | ||
|
||
positioner.arrangeElements(); | ||
positioner.visitWidgets(this::addRenderableWidget); | ||
|
||
List.of(this.searchBox, this.list).forEach(this::addRenderableWidget); | ||
this.setInitialFocus(this.list); | ||
|
||
this.initialized = true; | ||
} | ||
|
||
@Override | ||
public void renderBackground(GuiGraphics gfx, int mouseX, int mouseY, float delta) { | ||
super.renderBackground(gfx, mouseX, mouseY, delta); | ||
int left = (this.width - GUI_WIDTH) / 2; | ||
JUMP_BACKGROUND.draw(gfx, left, GUI_TOP, 0, GUI_WIDTH, this.getGuiHeight()); | ||
JUMP_LIST_BACKGROUND.draw(gfx, left + 7, LIST_TOP - 1, 0, this.list.getRowWidth() + 2, this.getListHeight() + 2); | ||
gfx.blitSprite(SEARCH_ICON_TEXTURE, left + 10, LIST_TOP + 3, 12, 12); | ||
} | ||
|
||
@Override | ||
public void tick() { | ||
super.tick(); | ||
if (this.initialized) { | ||
this.updateEntries(); | ||
} | ||
} | ||
|
||
@Override | ||
public boolean keyPressed(int keyCode, int scanCode, int modifiers) { | ||
if (this.searchBox.isFocused()) { | ||
if (keyCode == GLFW.GLFW_KEY_ESCAPE) { | ||
this.magicalSpecialHackyFocus(null); | ||
return true; | ||
} | ||
} else { | ||
if (KEY_GOTO_GUI.matches(keyCode, scanCode)) { | ||
this.onClose(); | ||
return true; | ||
} | ||
} | ||
if (this.list.getSelected() != null) { | ||
if (CommonInputs.selected(keyCode)) { | ||
this.go(); | ||
return true; | ||
} | ||
if (this.list.keyPressed(keyCode, scanCode, modifiers)) { | ||
return true; | ||
} | ||
} | ||
return super.keyPressed(keyCode, scanCode, modifiers); | ||
} | ||
|
||
@Override | ||
public boolean isPauseScreen() { | ||
return false; | ||
} | ||
|
||
public void updateEntries() { | ||
List<ListEntry> entries = calculatePlayerEntries().stream() | ||
.filter(entry -> this.currentSearch == null | ||
|| this.currentSearch.isEmpty() | ||
|| entry.matchesSearch(this.currentSearch)) | ||
.sorted() | ||
.toList(); | ||
|
||
// Update only if the list has changed | ||
if (!Objects.equals(this.list.children(), entries)) { | ||
this.list.updateEntries(entries); | ||
} | ||
} | ||
|
||
private List<ListEntry> calculatePlayerEntries() { | ||
// Store the existing entries in a UUID map for easy lookup | ||
Map<UUID, PlayerListEntry> currentEntries = this.list.children() | ||
.parallelStream() | ||
.filter(PlayerListEntry.class::isInstance) | ||
.map(PlayerListEntry.class::cast) | ||
.collect(Collectors.toUnmodifiableMap(PlayerListEntry::getUUID, entry -> entry)); | ||
|
||
// Map the in-range players into PlayerListEntries | ||
// Use existing entries if possible | ||
return this.minecraft.level.players() | ||
.parallelStream() | ||
.filter(player -> !(player instanceof FreeCamera)) | ||
.map(player -> Objects.requireNonNullElseGet( | ||
currentEntries.get(player.getUUID()), | ||
() -> new PlayerListEntry(this.minecraft, this, player))) | ||
.map(ListEntry.class::cast) | ||
.toList(); | ||
} | ||
|
||
public void go() { | ||
Optional.ofNullable(this.list.getSelected()) | ||
.ifPresent(listEntry -> { | ||
boolean perspective = this.buttonPerspective != null && this.buttonPerspective.active; | ||
this.onClose(); | ||
Freecam.gotoPosition(listEntry.getPosition(), listEntry.getName(), perspective); | ||
}); | ||
} | ||
|
||
public void select(ListEntry entry) { | ||
this.list.setSelected(entry); | ||
this.updateButtonState(); | ||
} | ||
|
||
public void updateButtonState() { | ||
ListEntry selected = this.list.getSelected(); | ||
this.buttonJump.active = selected != null; | ||
} | ||
|
||
private void onSearchChange(String search) { | ||
this.currentSearch = search.toLowerCase(Locale.ROOT); | ||
} | ||
|
||
// GUI height | ||
private int getGuiHeight() { | ||
return Math.max(52, this.height - (GUI_TOP * 2)); | ||
} | ||
|
||
// List height including search bar | ||
private int getListHeight() { | ||
return this.getGuiHeight() - 29 - 8; | ||
} | ||
|
||
private int getListBottom() { | ||
return LIST_TOP + this.getListHeight(); | ||
} | ||
} |
Oops, something went wrong.