Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Settings screen rewrite #510

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/main/java/net/vulkanmod/Initializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import net.fabricmc.loader.api.FabricLoader;
import net.vulkanmod.config.Config;
import net.vulkanmod.config.Platform;
import net.vulkanmod.config.video.VideoModeManager;
import net.vulkanmod.render.chunk.build.frapi.VulkanModRenderer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
Expand All @@ -30,7 +29,6 @@ public void onInitializeClient() {
LOGGER.info("== VulkanMod ==");

Platform.init();
VideoModeManager.init();

var configPath = FabricLoader.getInstance()
.getConfigDir()
Expand Down
58 changes: 14 additions & 44 deletions src/main/java/net/vulkanmod/config/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,43 @@

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.vulkanmod.config.video.VideoModeManager;
import net.vulkanmod.config.video.VideoModeSet;

import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;

public class Config {

private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.create();
private static Path CONFIG_PATH;
public int frameQueueSize = 2;
public VideoModeSet.VideoMode videoMode = VideoModeManager.getFirstAvailable().getVideoMode();
public boolean windowedFullscreen = false;

public int advCulling = 2;
public boolean indirectDraw = true;

public boolean backFaceCulling = true;
public boolean uniqueOpaqueLayer = true;
public boolean entityCulling = true;
public int device = -1;

public int ambientOcclusion = 1;

public boolean backFaceCulling = true;

public void write() {

if(!Files.exists(CONFIG_PATH.getParent())) {
try {
Files.createDirectories(CONFIG_PATH);
} catch (IOException e) {
e.printStackTrace();
}
public static Config load(Path path) {
CONFIG_PATH = path;
try (FileReader fileReader = new FileReader(path.toFile())) {
return GSON.fromJson(fileReader, Config.class);
} catch (IOException exception) {
return null;
}
}

public void write() {
try {
Files.createDirectories(CONFIG_PATH.getParent());
Files.write(CONFIG_PATH, Collections.singleton(GSON.toJson(this)));
} catch (IOException e) {
e.printStackTrace();
}
}

private static Path CONFIG_PATH;

private static final Gson GSON = new GsonBuilder()
.setPrettyPrinting()
.excludeFieldsWithModifiers(Modifier.PRIVATE)
.create();

public static Config load(Path path) {
Config config;
Config.CONFIG_PATH = path;

if (Files.exists(path)) {
try (FileReader fileReader = new FileReader(path.toFile())) {
config = GSON.fromJson(fileReader, Config.class);
}
catch (IOException exception) {
throw new RuntimeException(exception.getMessage());
}
}
else {
config = null;
}

return config;
}
}
269 changes: 269 additions & 0 deletions src/main/java/net/vulkanmod/config/gui/ConfigScreen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package net.vulkanmod.config.gui;

import net.minecraft.Util;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.gui.screens.options.VideoSettingsScreen;
import net.minecraft.network.chat.Component;
import net.vulkanmod.Initializer;
import net.vulkanmod.config.gui.container.OptionList;
import net.vulkanmod.config.gui.container.PageList;
import net.vulkanmod.config.gui.option.Option;
import net.vulkanmod.config.gui.option.OptionPage;
import net.vulkanmod.config.gui.util.Dimension;
import net.vulkanmod.config.gui.util.GuiConstants;
import net.vulkanmod.config.gui.util.SearchFieldTextModel;
import net.vulkanmod.config.gui.widget.ButtonWidget;
import net.vulkanmod.config.gui.widget.SearchFieldWidget;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;

import java.util.List;
import java.util.function.Predicate;

public class ConfigScreen extends Screen {
private final Screen parent;
private final List<OptionPage> optionPages;
private final SearchFieldTextModel searchFieldTextModel;
private List<OptionPage> currentOptionPages;
private SearchFieldWidget searchField;
private PageList pageList;
private Dimension<Integer> pageDim, headerDim, footerDim, pageListDim, optionListDim;
private ButtonWidget doneButton, applyButton, undoButton, kofiButton;

public ConfigScreen(Screen parent) {
super(Component.literal("VulkanMod Settings"));

this.parent = parent;
this.optionPages = List.of(
ConfigScreenPages.video(),
ConfigScreenPages.graphics(),
ConfigScreenPages.optimizations(),
ConfigScreenPages.other());
this.currentOptionPages = optionPages;
this.searchFieldTextModel = new SearchFieldTextModel(this::filterSearchResults);
}

@Override
public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float delta) {
GuiRenderer.guiGraphics = guiGraphics;
GuiRenderer.pose = guiGraphics.pose();

updateControls();

super.render(guiGraphics, mouseX, mouseY, delta);
}

@Override
protected void init() {
// Remember if the search field was previously focused since we'll lose that information after recreating the widget
boolean wasSearchFocused = false;
if (this.searchField != null)
wasSearchFocused = this.searchField.isFocused();

this.createDimensions();
this.createHeader();
this.createPageList();
this.createOptionList();
this.createFooter();

if (wasSearchFocused) {
this.setFocused(this.searchField);
}
}

@Override
public void rebuildWidgets() {
this.clearWidgets();
this.init();
}

private void createDimensions() {
final float aspectRatio = 4f / 3f;
final int minWidth = 550;

int newWidth = this.width;
if (newWidth > minWidth && (float) this.width / (float) this.height > aspectRatio) {
newWidth = Math.max(minWidth, (int) (this.height * aspectRatio));
}

this.pageDim = Dimension.ofInt(
(this.width - newWidth) / 2 + GuiConstants.WIDGET_MARGIN, GuiConstants.WIDGET_MARGIN,
newWidth - GuiConstants.WIDGET_MARGIN * 2, this.height - GuiConstants.WIDGET_MARGIN * 2);
this.headerDim = Dimension.ofInt(
pageDim.x(), pageDim.y(),
pageDim.width(), GuiConstants.WIDGET_HEIGHT);
this.footerDim = Dimension.ofInt(
pageDim.x(), pageDim.yLimit() - GuiConstants.WIDGET_HEIGHT,
pageDim.width(), GuiConstants.WIDGET_HEIGHT);
this.pageListDim = Dimension.ofInt(
pageDim.x(), pageDim.y(),
80, footerDim.y() - pageDim.y() - GuiConstants.WIDGET_MARGIN);
this.optionListDim = Dimension.ofInt(
pageListDim.xLimit() + GuiConstants.WIDGET_MARGIN, headerDim.yLimit() + GuiConstants.WIDGET_MARGIN,
pageDim.xLimit() - pageListDim.xLimit() - GuiConstants.WIDGET_MARGIN, footerDim.y() - headerDim.yLimit() - GuiConstants.WIDGET_MARGIN * 2);
}

private void createPageList() {
this.pageList = new PageList(pageListDim, this, currentOptionPages);
this.addRenderableWidget(pageList);
}

private void createOptionList() {
this.addRenderableWidget(new OptionList(optionListDim, this, pageList.getCurrentPage()));
}

private void createHeader() {
final int padding = 10;
final int minButtonWidth = 50;

String kofiKey = "vulkanmod.options.buttons.kofi";
int kofiButtonWidth = Math.max(this.font.width(Component.translatable(kofiKey)) + 2 * padding, minButtonWidth);
this.kofiButton = new ButtonWidget(
Dimension.ofInt(
this.headerDim.xLimit() - kofiButtonWidth, this.headerDim.y(),
kofiButtonWidth, this.headerDim.height()),
Component.translatable(kofiKey),
this::openDonationPage);

Dimension<Integer> searchFieldDim = Dimension.ofInt(
this.pageListDim.xLimit() + GuiConstants.WIDGET_MARGIN, this.headerDim.y(),
this.headerDim.width() - this.pageListDim.width() - kofiButtonWidth - GuiConstants.WIDGET_MARGIN * 2, this.headerDim.height());

this.searchField = new SearchFieldWidget(searchFieldDim, searchFieldTextModel);

this.addRenderableWidget(searchField);
this.addRenderableWidget(kofiButton);
}

private void createFooter() {
final int padding = 10;
final int minButtonWidth = 50;

String doneKey = "gui.done";
String applyKey = "vulkanmod.options.buttons.apply";
String undoKey = "vulkanmod.options.buttons.undo";

int doneButtonWidth = Math.max(this.font.width(Component.translatable(doneKey)) + 2 * padding, minButtonWidth);
int applyButtonWidth = Math.max(this.font.width(Component.translatable(applyKey)) + 2 * padding, minButtonWidth);
int undoButtonWidth = Math.max(this.font.width(Component.translatable(undoKey)) + 2 * padding, minButtonWidth);


int x0 = this.footerDim.xLimit() - doneButtonWidth;
this.doneButton = new ButtonWidget(
Dimension.ofInt(
x0, this.footerDim.y(),
doneButtonWidth, this.footerDim.height()),
Component.translatable(doneKey),
this::onClose);

x0 -= (applyButtonWidth + GuiConstants.WIDGET_MARGIN);
this.applyButton = new ButtonWidget(
Dimension.ofInt(
x0, this.footerDim.y(),
applyButtonWidth, this.footerDim.height()),
Component.translatable(applyKey),
this::applyChanges);

x0 -= (undoButtonWidth + GuiConstants.WIDGET_MARGIN);
this.undoButton = new ButtonWidget(
Dimension.ofInt(
x0, this.footerDim.y(),
undoButtonWidth, this.footerDim.height()),
Component.translatable(undoKey),
this::undoChanges);

this.applyButton.setActive(false);
this.undoButton.setVisible(false);

this.addRenderableWidget(undoButton);
this.addRenderableWidget(applyButton);
this.addRenderableWidget(doneButton);
}

private void applyChanges() {
pageList.getCurrentPage().options().forEach(Option::apply);
Initializer.CONFIG.write();
}

private void undoChanges() {
pageList.getCurrentPage().options().forEach(Option::undo);
}

private void updateControls() {
boolean hasChanges = this.optionPages.stream()
.flatMap(page -> page.options().stream())
.anyMatch(Option::isChanged);

this.applyButton.setActive(hasChanges);
this.undoButton.setVisible(hasChanges);

if (!this.applyButton.isActive()) this.applyButton.setFocused(false);
if (!this.undoButton.isActive()) this.undoButton.setFocused(false);
}

private void openDonationPage() {
Util.getPlatform().openUri("https://ko-fi.com/xcollateral");
}

private void filterSearchResults(@NotNull String query) {
Predicate<String> queryContains = string ->
string != null && string.toLowerCase()
.replaceAll("\\s+", "")
.contains(query.toLowerCase()
.replaceAll("\\s+", ""));

Predicate<Option<?>> nameFilter = option ->
queryContains.test(option.name().getString());
Predicate<Option<?>> tooltipFilter = option ->
queryContains.test(option.tooltip() != null
? option.tooltip().getString()
: null);
Predicate<Option<?>> valueFilter = option ->
queryContains.test(option.displayedValue().getString());

currentOptionPages = optionPages.stream()
.map(page -> page.filtered(nameFilter.or(tooltipFilter).or(valueFilter)))
.filter(page -> !page.options().isEmpty())
.toList();

rebuildWidgets();
}

@Override
public void onClose() {
Minecraft.getInstance().setScreen(parent);
}

@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
boolean clicked = super.mouseClicked(mouseX, mouseY, button);
if (clicked && (getFocused() instanceof PageList || // This is done so focus doesn't get "stuck" after clicking pageList buttons
(getFocused() instanceof SearchFieldWidget searchFieldWidget
&& !searchFieldWidget.isMouseOver(mouseX, mouseY)))) {
setFocused(null);
}

return clicked;
}

@Override
public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
if ((modifiers & (Minecraft.ON_OSX ? GLFW.GLFW_MOD_SUPER : GLFW.GLFW_MOD_CONTROL)) != 0 && keyCode == GLFW.GLFW_KEY_L) {
this.setFocused(searchField);

return true;
}

if (!(getFocused() instanceof SearchFieldWidget)
&& keyCode == GLFW.GLFW_KEY_P
&& (modifiers & GLFW.GLFW_MOD_SHIFT) != 0) {
Minecraft.getInstance().setScreen(new VideoSettingsScreen(this, Minecraft.getInstance(), Minecraft.getInstance().options));

return true;
}

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