Skip to content

Commit

Permalink
extensible quick-switcher
Browse files Browse the repository at this point in the history
API breakages to make this good soon - but this is a good PoC.
  • Loading branch information
sisby-folk committed Feb 19, 2023
1 parent 1b77105 commit 9611f3f
Show file tree
Hide file tree
Showing 21 changed files with 358 additions and 62 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package folk.sisby.switchy.client;

import folk.sisby.switchy.Switchy;
import folk.sisby.switchy.client.api.SwitchyClientModInitializer;
import org.quiltmc.loader.api.ModContainer;
import org.quiltmc.loader.api.QuiltLoader;
import org.quiltmc.qsl.base.api.entrypoint.client.ClientModInitializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -18,6 +20,10 @@ public void onInitializeClient(ModContainer mod) {
SwitchyCommandsClient.InitializeCommands();
SwitchyClientNetworking.InitializeReceivers();
SwitchyKeybinds.initializeKeybinds();

for(SwitchyClientModInitializer init : QuiltLoader.getEntrypoints(ID, SwitchyClientModInitializer.class)) {
init.onInitialize();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package folk.sisby.switchy.client.api;

@FunctionalInterface
public interface SwitchyClientModInitializer {
void onInitialize();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package folk.sisby.switchy.client.api;

import folk.sisby.switchy.client.presets.SwitchyDisplayPreset;
import folk.sisby.switchy.client.screen.SwitchScreen.ComponentPosition;
import io.wispforest.owo.ui.core.Component;
import net.minecraft.util.Identifier;

import java.util.function.Function;

import static folk.sisby.switchy.client.screen.SwitchScreen.registerPresetDisplayComponent;

public class SwitchyScreenExtensions {
public static void registerQuickSwitchDisplayComponent(Identifier id, ComponentPosition pos, Function<SwitchyDisplayPreset, Component> componentFunction) {
registerPresetDisplayComponent(id, pos, componentFunction);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import folk.sisby.switchy.client.presets.SwitchyDisplayPresets;
import io.wispforest.owo.ui.base.BaseOwoScreen;
import io.wispforest.owo.ui.component.Components;
import io.wispforest.owo.ui.container.Containers;
import io.wispforest.owo.ui.container.FlowLayout;
import io.wispforest.owo.ui.container.HorizontalFlowLayout;
import io.wispforest.owo.ui.component.LabelComponent;
import io.wispforest.owo.ui.container.*;
import io.wispforest.owo.ui.core.*;
import net.minecraft.client.MinecraftClient;
import net.minecraft.text.Text;
Expand All @@ -21,10 +20,24 @@

public class SwitchScreen extends BaseOwoScreen<FlowLayout> {
public final SwitchyDisplayPresets displayPresets;
private static final Map<Identifier, Function<SwitchyDisplayPreset, Component>> components = new HashMap<>();
public enum ComponentPosition {
SIDE_LEFT,
LEFT,
RIGHT,
SIDE_RIGHT
}
private static final Map<Identifier, Function<SwitchyDisplayPreset, Component>> sideLeftComponents = new LinkedHashMap<>();
private static final Map<Identifier, Function<SwitchyDisplayPreset, Component>> leftComponents = new LinkedHashMap<>();
private static final Map<Identifier, Function<SwitchyDisplayPreset, Component>> rightComponents = new LinkedHashMap<>();
private static final Map<Identifier, Function<SwitchyDisplayPreset, Component>> sideRightComponents = new LinkedHashMap<>();

public static void registerPresetDisplayComponent(Identifier id, Function<SwitchyDisplayPreset, Component> componentFunction) {
components.put(id, componentFunction);
public static void registerPresetDisplayComponent(Identifier id, ComponentPosition pos, Function<SwitchyDisplayPreset, Component> componentFunction) {
switch (pos) {
case SIDE_LEFT -> sideLeftComponents.put(id, componentFunction);
case LEFT -> leftComponents.put(id, componentFunction);
case RIGHT -> rightComponents.put(id, componentFunction);
case SIDE_RIGHT -> sideRightComponents.put(id, componentFunction);
}
}


Expand All @@ -38,42 +51,75 @@ public SwitchScreen(SwitchyDisplayPresets displayPresets) {
return OwoUIAdapter.create(this, Containers::verticalFlow);
}

private Component generatePresetComponent(SwitchyDisplayPreset preset) {
// Main Horizontal Flow Panel
HorizontalFlowLayout horizontalFLow = Containers.horizontalFlow(Sizing.fixed(400), Sizing.content());
horizontalFLow.padding(Insets.vertical(4).withLeft(10).withRight(10));
horizontalFLow.gap(2);
horizontalFLow.surface(Surface.DARK_PANEL);
horizontalFLow.verticalAlignment(VerticalAlignment.CENTER);
horizontalFLow.horizontalAlignment(HorizontalAlignment.CENTER);
horizontalFLow.mouseEnter().subscribe(() -> horizontalFLow.surface(Surface.DARK_PANEL.and(Surface.outline(Color.WHITE.argb()))));
horizontalFLow.mouseLeave().subscribe(() -> horizontalFLow.surface(Surface.DARK_PANEL));
horizontalFLow.mouseDown().subscribe((x, y, button) -> {
SwitchyClientNetworking.sendSwitch(preset.presetName);
return true;
});

// Left Side Elements
horizontalFLow.children(sideLeftComponents.values().stream().map((fun) -> fun.apply(preset)).filter(Objects::nonNull).toList());

// Main Elements
HorizontalFlowLayout leftRightFlow = Containers.horizontalFlow(Sizing.content(), Sizing.content());
leftRightFlow.gap(4);

VerticalFlowLayout leftAlignedFlow = Containers.verticalFlow(Sizing.content(), Sizing.content());
leftAlignedFlow.horizontalAlignment(HorizontalAlignment.LEFT);
leftAlignedFlow.gap(2);
leftAlignedFlow.children(leftComponents.values().stream().map((fun) -> fun.apply(preset)).filter(Objects::nonNull).toList());
leftRightFlow.child(leftAlignedFlow);

VerticalFlowLayout rightAlignedFlow = Containers.verticalFlow(Sizing.content(), Sizing.content());
rightAlignedFlow.horizontalAlignment(HorizontalAlignment.RIGHT);
rightAlignedFlow.gap(2);
rightAlignedFlow.children(rightComponents.values().stream().map((fun) -> fun.apply(preset)).filter(Objects::nonNull).toList());
leftRightFlow.child(rightAlignedFlow);

horizontalFLow.child(leftRightFlow);

// Right Side Elements
horizontalFLow.children(sideRightComponents.values().stream().map((fun) -> fun.apply(preset)).filter(Objects::nonNull).toList());

return horizontalFLow;
}

@Override
protected void build(FlowLayout rootComponent) {
rootComponent
.surface(Surface.VANILLA_TRANSLUCENT)
.horizontalAlignment(HorizontalAlignment.CENTER)
.verticalAlignment(VerticalAlignment.CENTER);

List<Component> presets = new ArrayList<>();
displayPresets.presets.forEach((name, preset) -> {
HorizontalFlowLayout presetFlow = Containers.horizontalFlow(Sizing.content(), Sizing.content());
presetFlow.children(components.values().stream().map((fun) -> fun.apply(preset)).toList());
presetFlow.padding(Insets.of(10));
presetFlow.margins(Insets.vertical(2));
presetFlow.surface(Surface.DARK_PANEL);
presetFlow.mouseDown().subscribe((x, y, button) -> {
SwitchyClientNetworking.sendSwitch(name);
return true;
});
presetFlow.mouseEnter().subscribe(() -> {
presetFlow.surface(Surface.DARK_PANEL.and(Surface.outline(Color.WHITE.argb())));
});
presetFlow.mouseLeave().subscribe(() -> {
presetFlow.surface(Surface.DARK_PANEL);
});
presets.add(presetFlow);
});
rootComponent.surface(Surface.VANILLA_TRANSLUCENT);
rootComponent.horizontalAlignment(HorizontalAlignment.CENTER);
rootComponent.verticalAlignment(VerticalAlignment.CENTER);

List<Component> presetFlows = new ArrayList<>(displayPresets.presets.values().stream().map(this::generatePresetComponent).toList());

VerticalFlowLayout presetsLayout = Containers.verticalFlow(Sizing.content(), Sizing.content());
presetsLayout.padding(Insets.of(6));
presetsLayout.verticalAlignment(VerticalAlignment.CENTER);
presetsLayout.horizontalAlignment(HorizontalAlignment.CENTER);
presetsLayout.gap(4);
presetsLayout.children(presetFlows);

ScrollContainer<VerticalFlowLayout> presetsScroll = Containers.verticalScroll(Sizing.content(), Sizing.fill(80), presetsLayout);
presetsScroll.surface(Surface.DARK_PANEL);
presetsScroll.padding(Insets.of(4));

LabelComponent screenLabel = Components.label(Text.literal("Switchy Presets"));

VerticalFlowLayout screenLabelFlow = Containers.verticalFlow(Sizing.content(), Sizing.content());
screenLabelFlow.horizontalAlignment(HorizontalAlignment.CENTER);
screenLabelFlow.gap(2);
screenLabelFlow.children(List.of(screenLabel, presetsScroll));

rootComponent.child(
Containers.verticalScroll(Sizing.content(), Sizing.fill(80),
Containers.verticalFlow(Sizing.content(), Sizing.content())
.children(presets)
.padding(Insets.of(10))
.verticalAlignment(VerticalAlignment.CENTER)
.horizontalAlignment(HorizontalAlignment.CENTER)
).surface(Surface.DARK_PANEL)
);
rootComponent.child(screenLabelFlow);
}

static {
Expand All @@ -87,6 +133,6 @@ protected void build(FlowLayout rootComponent) {
});

// Add base components
registerPresetDisplayComponent(new Identifier(SwitchyClient.ID, "preset_name"), displayPreset -> Components.label(Text.literal(displayPreset.presetName)));
registerPresetDisplayComponent(new Identifier(SwitchyClient.ID, "preset_name"), ComponentPosition.LEFT, displayPreset -> Components.label(Text.literal(displayPreset.presetName)));
}
}
10 changes: 6 additions & 4 deletions compat/build.gradle
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
dependencies {
implementation(project(path: ':core', configuration: 'namedElements'))
implementation(project(path: ':client', configuration: 'namedElements'))

// Redo Client Deps (why)
modCompileOnly libs.bundles.client

// Optional Compatible Mods
modCompileOnly libs.bundles.compat.compile
modLocalRuntime libs.bundles.compat.runtime
modCompileOnly(libs.origins) {
exclude module: 'AdditionalEntityAttributes'
exclude module: 'apoli'
}
modLocalRuntime(libs.origins) {
exclude module: 'calio'
modCompileOnly(libs.apoli) {
exclude module: 'AdditionalEntityAttributes'
}

}
1 change: 1 addition & 0 deletions compat/src/main/java/folk/sisby/switchy/SwitchyCompat.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.quiltmc.loader.api.QuiltLoader;

public class SwitchyCompat implements SwitchyModInitializer {
public static final String ID = "switchy_compat";

@Override
public void initializeSwitchyCompat() {
Expand Down
33 changes: 33 additions & 0 deletions compat/src/main/java/folk/sisby/switchy/SwitchyCompatClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package folk.sisby.switchy;

import folk.sisby.switchy.client.api.SwitchyClientModInitializer;
import folk.sisby.switchy.modules.DrogtorCompatClient;
import folk.sisby.switchy.modules.FabricTailorCompatClient;
import folk.sisby.switchy.modules.OriginsCompatClient;
import folk.sisby.switchy.modules.StyledNicknamesCompatClient;
import org.quiltmc.loader.api.QuiltLoader;

public class SwitchyCompatClient implements SwitchyClientModInitializer {
@Override
public void onInitialize() {
// Basically recreate the functionality of every mod because it might not be on the client (ow)
// We can definitely do better than this for API. writing two NBT parsers makes no sense.

// Resolution: We need to take a path that allows compat modules to be moved into their respective mods
// In this case, this really does mean adding client-side addon functionality to server-sided mods like styled.
// So yes, you may literally need to install styled nicknames onto your client. Patbox is crying. Everyone is crying.

// If it's serializable or packetable though, we'd be able to parse The module data into something usable on
// the client side *before* sending it. So that's good.

// I'd say we probably want a Serializer that handles field storage, and an extension of that interface
// Think: DrogtorServerModule extends DrogtorModuleSerializer implements SwitchyModule (<DrogtorModuleSerializer>?)
// interface SwitchyModule extends SwitchyModuleSerializer
// interface SwitchyClientModule extends SwitchyModuleSerializer

DrogtorCompatClient.touch();
StyledNicknamesCompatClient.touch();
FabricTailorCompatClient.touch();
if (QuiltLoader.isModLoaded("origins")) OriginsCompatClient.touch();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public void applyToPlayer(PlayerEntity player) {
}

@Override
public NbtCompound toNbt() {
public NbtCompound toNbt(boolean displayOnly) {
NbtCompound outNbt = new NbtCompound();
if (this.nickname != null) outNbt.putString(KEY_NICKNAME, this.nickname);
if (this.namecolor != null) outNbt.putString(KEY_NAME_COLOR, this.namecolor.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package folk.sisby.switchy.modules;

import folk.sisby.switchy.client.api.SwitchyScreenExtensions;
import folk.sisby.switchy.client.screen.SwitchScreen.ComponentPosition;
import io.wispforest.owo.ui.component.Components;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.text.HoverEvent;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;

public class DrogtorCompatClient {
public static final Identifier ID = new Identifier("switchy", "drogtor");

public static final String KEY_NICKNAME = "nickname";
public static final String KEY_NAME_COLOR = "nameColor";
public static final String KEY_BIO = "bio";


public static void touch() {}

static {
SwitchyScreenExtensions.registerQuickSwitchDisplayComponent(ID, ComponentPosition.RIGHT, displayPreset -> {
if (!displayPreset.modules.containsKey(ID)) return null;
NbtCompound nbt = displayPreset.modules.get(ID);
if (!nbt.contains(KEY_NICKNAME)) return null;
Style style = Style.EMPTY;
if (nbt.contains(KEY_NAME_COLOR)) style = style.withColor(Formatting.byName(nbt.getString(KEY_NAME_COLOR)));
if (nbt.contains(KEY_BIO)) style = style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Text.literal(nbt.getString(KEY_BIO))));
return Components.label(Text.literal(nbt.getString(KEY_NICKNAME)).setStyle(style));
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public void applyToPlayer(PlayerEntity player) {
}

@Override
public NbtCompound toNbt() {
public NbtCompound toNbt(boolean displayOnly) {
NbtCompound outNbt = new NbtCompound();
if (this.skinValue != null) outNbt.putString(KEY_SKIN_VALUE, this.skinValue);
if (this.skinSignature != null) outNbt.putString(KEY_SKIN_SIGNATURE, this.skinSignature);

return outNbt;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package folk.sisby.switchy.modules;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mojang.authlib.minecraft.MinecraftProfileTexture;
import com.mojang.authlib.yggdrasil.response.MinecraftTexturesPayload;
import com.mojang.util.UUIDTypeAdapter;
import folk.sisby.switchy.client.api.SwitchyScreenExtensions;
import folk.sisby.switchy.client.screen.SwitchScreen;
import io.wispforest.owo.ui.component.Components;
import io.wispforest.owo.ui.component.EntityComponent;
import io.wispforest.owo.ui.core.Sizing;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.AbstractClientPlayerEntity;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.util.Identifier;

import java.util.Base64;
import java.util.UUID;

public class FabricTailorCompatClient {
public static final Identifier ID = new Identifier("switchy", "fabric_tailor");

public static final String KEY_SKIN_VALUE = "skinValue";
public static final String KEY_SKIN_SIGNATURE = "skinSignature";

public static void touch() {}

static {
SwitchyScreenExtensions.registerQuickSwitchDisplayComponent(ID, SwitchScreen.ComponentPosition.SIDE_RIGHT, displayPreset -> {
if (!displayPreset.modules.containsKey(ID)) return null;
NbtCompound nbt = displayPreset.modules.get(ID);
if (!nbt.contains(KEY_SKIN_VALUE) || !nbt.contains(KEY_SKIN_SIGNATURE)) return null;
MinecraftClient client = MinecraftClient.getInstance();

String value = nbt.getString(KEY_SKIN_VALUE);
Gson gson = new GsonBuilder().registerTypeAdapter(UUID.class, new UUIDTypeAdapter()).create();
MinecraftTexturesPayload payload = gson.fromJson(new String(Base64.getDecoder().decode(value)), MinecraftTexturesPayload.class);
MinecraftProfileTexture skinTexture = payload.getTextures().get(MinecraftProfileTexture.Type.SKIN);

Identifier skinId = client.getSkinProvider().loadSkin(skinTexture, MinecraftProfileTexture.Type.SKIN);

EntityComponent<AbstractClientPlayerEntity> skinPreview = Components.entity(Sizing.fixed(60), new AbstractClientPlayerEntity(client.world, client.getSession().getProfile(), null) {
@Override
public Identifier getSkinTexture() {
return skinId;
}
});

skinPreview.scale(0.5F);

return skinPreview;
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,15 @@ private static void setOrigin(PlayerEntity player, OriginLayer layer, Origin ori
}

@Override
public NbtCompound toNbt() {
public NbtCompound toNbt(boolean displayOnly) {
NbtCompound outNbt = new NbtCompound();
// From Origins PlayerOriginComponent
NbtList originLayerList = new NbtList();
if (this.origins != null) {
for (Map.Entry<OriginLayer, Origin> entry : origins.entrySet()) {
NbtCompound layerTag = new NbtCompound();
layerTag.putString("Layer", entry.getKey().getIdentifier().toString());
layerTag.putString("Origin", entry.getValue().getIdentifier().toString());
layerTag.putString("Origin", displayOnly ? entry.getValue().getName().getString() : entry.getValue().getIdentifier().toString());
originLayerList.add(layerTag);
}
}
Expand Down
Loading

0 comments on commit 9611f3f

Please sign in to comment.