Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package org.vivecraft.client.gui.framework.screens;

import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import org.vivecraft.client.gui.framework.widgets.SettingsList;

import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public abstract class GuiGroupedListEditorScreen<T> extends GuiListScreen {
private final Supplier<List<T>> valuesSupplier;
private final Function<T, String> categorySupplier;
private final Runnable loadDefaults;
private final Consumer<List<T>> save;

protected final boolean fixedEntryCount;

protected List<T> elements;
protected abstract T createNewValue(String category);

public GuiGroupedListEditorScreen(
Component title, Screen lastScreen, boolean fixedEntryCount, Supplier<List<T>> valuesSupplier,
Runnable loadDefaults, Consumer<List<T>> save, @Nullable Function<T, String> categorySupplier)
{
super(title, lastScreen);
this.fixedEntryCount = fixedEntryCount;
this.valuesSupplier = valuesSupplier;
this.loadDefaults = loadDefaults;
this.save = save;
this.categorySupplier = categorySupplier != null ? categorySupplier : item -> "Empty";
}

@Override
protected void init() {
super.init();
}

@Override
protected void addLowerButtons(int top) {
this.addRenderableWidget(
Button.builder(Component.translatable("vivecraft.gui.loaddefaults"), button -> {
this.loadDefaults.run();
this.elements = null;
this.reinit = true;
})
.bounds(this.width / 2 - 155, top, 150, 20)
.build());

this.addRenderableWidget(
Button.builder(Component.translatable("gui.back"), button -> this.onClose())
.bounds(this.width / 2 + 5, top, 150, 20)
.build());
}

@Override
public void onClose() {
this.save.accept(this.elements);
super.onClose();
}

@SuppressWarnings("unchecked")
protected List<T> getCurrentValues() {
return this.list.children().stream().map(entry -> {
if (entry instanceof GuiListEditorScreen.ValueEntry<?> valueEntry) {
return (T) valueEntry.getValue();
} else {
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
}

@Override
protected List<SettingsList.BaseEntry> getEntries() {
List<SettingsList.BaseEntry> entries = new LinkedList<>();

if (this.elements == null) {
this.elements = new ArrayList<>(this.valuesSupplier.get());
}

Map<String, List<T>> grouped = new LinkedHashMap<>();

for (T element : this.elements) {
String category = this.categorySupplier.apply(element);
grouped.computeIfAbsent(category, k -> new LinkedList<>())
.add(element);
}

int index = 0;
for (Map.Entry<String, List<T>> group : grouped.entrySet()) {

String category = group.getKey();
List<T> values = group.getValue();

if (!category.isEmpty()) {
entries.add(new SettingsList.GroupedEntry(
Component.translatable(category)
));
}

for (T value : values) {
entries.add(this.toEntry(value, index++));
}

if (!this.fixedEntryCount && !category.isEmpty()) {
entries.add(createAddButton(category));
}
}

return entries;
}

private SettingsList.BaseEntry createAddButton(String category) {
return new SettingsList.WidgetEntry(
Component.literal(""),
Button.builder(Component.translatable("vivecraft.options.add"), button -> {
this.addNewValue(category);
}).size(50, 20).build()
);
}

protected void addNewValue(String category) {
this.elements = getCurrentValues();
this.elements.add(this.createNewValue(category));
this.reinit = true;
}

protected abstract ValueEntry<T> toEntry(T value, int index);

protected static abstract class ValueEntry<T> extends SettingsList.BaseEntry {
public ValueEntry(Component name, Supplier<String> tooltipSupplier) {
super(name, tooltipSupplier);
}

public abstract T getValue();
}
}
Original file line number Diff line number Diff line change
@@ -1,58 +1,97 @@
package org.vivecraft.client.gui.settings;

import com.google.common.collect.ImmutableList;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphics;
import net.minecraft.client.gui.components.AbstractWidget;
import net.minecraft.client.gui.components.Button;
import net.minecraft.client.gui.components.Tooltip;
import net.minecraft.client.gui.components.events.GuiEventListener;
import net.minecraft.client.gui.narration.NarratableEntry;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import org.vivecraft.client.gui.framework.screens.GuiListScreen;
import org.vivecraft.client.gui.framework.widgets.SettingsList;
import org.vivecraft.client_vr.provider.control.ActionType;
import org.vivecraft.client_vr.provider.control.InputAction;
import org.vivecraft.client.gui.framework.screens.GuiGroupedListEditorScreen;
import org.vivecraft.client_vr.provider.control.ActionSet;
import org.vivecraft.client_vr.provider.control.Source;
import org.vivecraft.client_vr.provider.openxr.MCOpenXR;
import org.vivecraft.client_vr.provider.control.BindingProfile;

import java.util.LinkedList;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import java.util.Map;

public class GuiBindings extends GuiListScreen {
public class GuiBindings extends GuiGroupedListEditorScreen<Source> {
public GuiBindings(Screen lastScreen) {
super(Component.empty(), lastScreen, false, () ->
{
if (MCOpenXR.get() == null) return List.of();
List<Source> sources = new ArrayList<>();
try {
if (MCOpenXR.get() == null) return List.of();
BindingProfile profile = BindingProfile.getCurrentProfile();
if (profile == null) profile = BindingProfile.getDefaultBinding(MCOpenXR.get().getCurrentInteractionProfile());
if (profile == null) profile = BindingProfile.getDefaultBinding("");
for (ActionSet set : profile.sets().values()) {sources.addAll(set.sources());}
} catch (FileNotFoundException e) {
// Show error to user about missing profile
}

return sources;
},
() -> {},
save -> {
Map<String, ActionSet> bindings = new HashMap<>();
bindings.put("/actions/custom",
new ActionSet(save, null, null, null)
);

public GuiBindings(Screen lastScreen) {
super(Component.translatable("vivecraft.options.screen.bindings"), lastScreen);
BindingProfile profile = new BindingProfile(
"Custom Profile",
"New custom profile",
"Custom",
null,
bindings
);

if (profile.saveProfile()) {
// TODO success message
} else {
// TODO error message
}
},
action -> Component.translatable(action.path()).getString()
);

this.searchable = false;
}

@Override
protected List<SettingsList.BaseEntry> getEntries() {
List<SettingsList.BaseEntry> list = new LinkedList<>();
for (var entry : MCOpenXR.get().getBinds().entrySet()) {
var input = MCOpenXR.get().getInputActionByName(entry.getKey());
if (input == null) continue;
//list.add(new ResettableEntry(Component.translatable(input.name), input));
}
return list;
protected Source createNewValue(String category) {
return null;
}

public static class ResettableEntry extends SettingsList.WidgetEntry {
public static final int VALUE_BUTTON_WIDTH = 125;
@Override
protected ValueEntry<Source> toEntry(Source value, int index) {
return new RemovableEntry<>(Component.translatable(value.path()), value, 0);
}

private final Button resetButton;
private final BooleanSupplier canReset;
protected static class RemovableEntry<T> extends ValueEntry<T> {
protected final T value;

public ResettableEntry(Component name, InputAction action) {
super(name, getBaseWidget(action, VALUE_BUTTON_WIDTH, 20).get());
private final int index;
private final Button editButton;
private final Button removeButton;

this.canReset = () -> action.type != ActionType.BOOLEAN;
this.resetButton = Button.builder(Component.literal("X"), button -> {
action.setType(ActionType.BOOLEAN);
this.valueWidget = getBaseWidget(action, valueWidget.getWidth(), valueWidget.getHeight()).get();
})
.tooltip(Tooltip.create(Component.translatable("controls.reset")))
public RemovableEntry(Component name, T value, int index) {
super(name, null);
this.value = value;
this.index = index;

this.editButton = Button.builder(Component.translatable("vivecraft.options.edit"),
button -> {})
.bounds(0, 0, 100, 20).build();
this.removeButton = Button.builder(Component.literal("-"),
button -> {})
.bounds(0, 0, 20, 20).build();
}

Expand All @@ -61,34 +100,41 @@ public void renderContent(
GuiGraphics guiGraphics, int mouseX, int mouseY, boolean hovering, float partialTick)
{
super.renderContent(guiGraphics, mouseX, mouseY, hovering, partialTick);
this.resetButton.setX(this.getContentRight() - 20);
this.resetButton.setY(this.getContentY());
this.resetButton.active = this.canReset.getAsBoolean();
this.resetButton.render(guiGraphics, mouseX, mouseY, partialTick);

int textY = this.getY() + this.getHeight() / 2 - Minecraft.getInstance().font.lineHeight / 2 + 2;
guiGraphics.drawString(Minecraft.getInstance().font, this.name, this.getContentX(), textY,
this.textColor());
this.removeButton.active = this.isActive();
this.editButton.active = this.isActive();

this.editButton.setX(this.getContentRight() - this.editButton.getWidth() - 20);
this.editButton.setY(this.getY());
this.editButton.render(guiGraphics, mouseX, mouseY, partialTick);
this.removeButton.setX(this.getContentRight() - 20);
this.removeButton.setY(this.getY());
this.removeButton.render(guiGraphics, mouseX, mouseY, partialTick);
}

@Override
public List<? extends GuiEventListener> children() {
return ImmutableList.of(this.valueWidget, this.resetButton);
public List<? extends NarratableEntry> narratables() {
return ImmutableList.of(this.editButton, this.removeButton);
}

@Override
public List<? extends NarratableEntry> narratables() {
return ImmutableList.of(this.valueWidget, this.resetButton);
public List<? extends GuiEventListener> children() {
return ImmutableList.of(this.editButton, this.removeButton);
}

@Override
public void setActive(boolean active) {
super.setActive(active);
this.resetButton.active = active;
this.removeButton.active = active;
this.editButton.active = active;
}
}

public static Supplier<AbstractWidget> getBaseWidget(InputAction action, int width, int height) {
return () -> Button
.builder(Component.literal("" + action.type), button -> {})
.bounds(0, 0, width, height)
.tooltip(Tooltip.create(Component.literal(action.name)))
.build();
@Override
public T getValue() {
return this.value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.vivecraft.client_vr.provider.control;

public record Action(String output, ActionType type) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package org.vivecraft.client_vr.provider.control;

import java.util.List;

public record ActionSet(
List<Source> sources,
List<Haptic> haptics,
List<Pose> poses,
List<Chord> chords
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,44 @@ public enum ActionType {
public String getSteamName() {
return steamName;
}

public static ActionType convertFromSteam(String steamName, String mode) {
return switch (steamName) {
case "click" -> switch (mode) {
case "button" -> BOOLEAN;
case "toggle_button" -> TOGGLE;
default -> BOOLEAN;
};
case "long" -> switch (mode) {
case "button" -> LONG_PRESS;
default -> LONG_PRESS;
};
case "scroll" -> switch (mode) {
case "scroll" -> VEC2;
default -> VEC2;
};
case "touch" -> switch (mode) {
case "trackpad" -> BOOLEAN;
default -> BOOLEAN;
};
case "position" -> switch (mode) {
case "joystick" -> VEC2;
default -> VEC2;
};
case "pull" -> switch (mode) {
case "trigger" -> VEC1;
default -> VEC1;
};
case "grab" -> switch (mode) {
case "grab" -> BOOLEAN;
default -> BOOLEAN;
};
case "north", "east", "south", "west" -> switch (mode) {
case "dpad" -> VEC1;
default -> VEC1;
};

default -> null;
};
}
}
Loading