init
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
package dev.photosync.platform;
|
||||
|
||||
import dev.photosync.mcapi.Clipboard;
|
||||
import net.minecraft.client.Minecraft;
|
||||
|
||||
/**
|
||||
* The system clipboard, via the keyboard handler that already owns GLFW's.
|
||||
*
|
||||
* <p>Going through Minecraft rather than AWT matters on macOS, where touching
|
||||
* {@code java.awt} from the render thread of a GLFW application is a hang.
|
||||
*/
|
||||
public final class ClipboardAdapter implements Clipboard {
|
||||
|
||||
@Override
|
||||
public String read() {
|
||||
return Minecraft.getInstance().keyboardHandler.getClipboard();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(String text) {
|
||||
Minecraft.getInstance().keyboardHandler.setClipboard(text);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package dev.photosync.platform;
|
||||
|
||||
import dev.photosync.mcapi.ClientBridge;
|
||||
import dev.photosync.platform.impl.CaptureAdapter;
|
||||
import dev.photosync.platform.impl.GameAdapter;
|
||||
import dev.photosync.platform.impl.ScreenAdapter;
|
||||
import dev.photosync.platform.impl.TextureAdapter;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
/**
|
||||
* Assembles this bucket's adapters into the one object the rest of the mod sees.
|
||||
*
|
||||
* <p>Built once, at client startup, before Minecraft has finished constructing
|
||||
* itself -- so nothing here may touch the game. Each adapter resolves what it
|
||||
* needs when it is first used instead.
|
||||
*/
|
||||
@Getter
|
||||
@Accessors(fluent = true)
|
||||
public final class MinecraftBridge implements ClientBridge {
|
||||
|
||||
private final GameAdapter game = new GameAdapter();
|
||||
private final TextAdapter text = new TextAdapter();
|
||||
private final ClipboardAdapter clipboard = new ClipboardAdapter();
|
||||
private final TextureAdapter textures = new TextureAdapter();
|
||||
private final ScreenAdapter screens = new ScreenAdapter();
|
||||
private final CaptureAdapter screenshots = new CaptureAdapter();
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package dev.photosync.platform;
|
||||
|
||||
import dev.photosync.client.PhotoSyncClient;
|
||||
import dev.photosync.platform.impl.OpenKey;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.fabricmc.api.ClientModInitializer;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Fabric's way in, and the mixins' way back.
|
||||
*
|
||||
* <p>Three lines of wiring: build the client against this bucket's bridge, tick
|
||||
* it, and close it when the game is shutting down. Everything else the mod does
|
||||
* is reached from those.
|
||||
*
|
||||
* <p>The static field is the same concession {@code ScreenshotBus} and
|
||||
* {@code QuitGuard} make, for the same reason -- a mixin is woven into a class
|
||||
* nobody constructs, so it cannot be handed a collaborator. Those two cover the
|
||||
* capture and quit hooks between them; this covers the third, the HUD, which
|
||||
* needs the client itself because it draws through it. Keeping the field here
|
||||
* rather than in each bucket's mixin package means there is exactly one of them.
|
||||
*/
|
||||
@Slf4j
|
||||
public final class PhotoSyncMod implements ClientModInitializer {
|
||||
|
||||
private static volatile PhotoSyncClient client;
|
||||
|
||||
@Override
|
||||
public void onInitializeClient() {
|
||||
OpenKey openKey = OpenKey.register();
|
||||
PhotoSyncClient started = new PhotoSyncClient(new MinecraftBridge()).start();
|
||||
client = started;
|
||||
|
||||
ClientTickEvents.END_CLIENT_TICK.register(game -> {
|
||||
started.tick();
|
||||
// Drained in a loop: holding the key over a lag spike queues several
|
||||
// presses, and opening the screen three times is not what was meant.
|
||||
boolean pressed = false;
|
||||
while (openKey.wasPressed()) {
|
||||
pressed = true;
|
||||
}
|
||||
if (pressed) {
|
||||
started.openScreen();
|
||||
}
|
||||
});
|
||||
|
||||
ClientLifecycleEvents.CLIENT_STOPPING.register(game -> {
|
||||
client = null;
|
||||
started.close();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The running client, for the HUD mixin.
|
||||
*
|
||||
* <p>Empty before {@code onInitializeClient} finishes and again once the
|
||||
* game is stopping, both of which are frames the HUD can still be drawn in.
|
||||
*/
|
||||
public static Optional<PhotoSyncClient> client() {
|
||||
return Optional.ofNullable(client);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package dev.photosync.platform;
|
||||
|
||||
import dev.photosync.mcapi.Translator;
|
||||
import net.minecraft.client.resources.language.I18n;
|
||||
import net.minecraft.locale.Language;
|
||||
|
||||
/**
|
||||
* Translations, from the language the player actually selected.
|
||||
*
|
||||
* <p>{@code I18n.get} falls back to the key when there is no entry, which is
|
||||
* what {@link Translator} promises. The existence check goes to {@link Language}
|
||||
* rather than {@code I18n.exists} because that method was dropped in 26.1 while
|
||||
* {@code Language.has} has been there throughout.
|
||||
*/
|
||||
public final class TextAdapter implements Translator {
|
||||
|
||||
@Override
|
||||
public String get(String key, Object... arguments) {
|
||||
return I18n.get(key, arguments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean has(String key) {
|
||||
return Language.getInstance().has(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* The half of the Minecraft adapter that has not changed since 1.20.
|
||||
*
|
||||
* <p>This is a source root, not a module: it imports {@code net.minecraft}, so
|
||||
* it is compiled once per compatibility bucket against that bucket's Minecraft
|
||||
* rather than published as a jar. The root build adds it to every
|
||||
* {@code :platform:*} project.
|
||||
*
|
||||
* <p>A class earns its place here by being <em>textually identical</em> on all
|
||||
* nine buckets -- verified by the fact that nine compilations accept it. The
|
||||
* moment a version needs a different body, the class moves down into
|
||||
* {@link dev.photosync.platform.impl}, which every bucket supplies for itself.
|
||||
* That is the whole rule; there is no third category.
|
||||
*/
|
||||
package dev.photosync.platform;
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"id": "${mod_id}",
|
||||
"version": "${mod_version}",
|
||||
"name": "${mod_name}",
|
||||
"description": "${mod_description}",
|
||||
"license": "${mod_license}",
|
||||
"environment": "client",
|
||||
"contact": {
|
||||
"sources": "${mod_sources}"
|
||||
},
|
||||
"entrypoints": {
|
||||
"client": [
|
||||
"dev.photosync.platform.PhotoSyncMod"
|
||||
]
|
||||
},
|
||||
"mixins": [
|
||||
{
|
||||
"config": "${mod_id}.mixins.json",
|
||||
"environment": "client"
|
||||
}
|
||||
],
|
||||
"depends": {
|
||||
"fabricloader": ">=${loader_version}",
|
||||
"fabric-api": "*",
|
||||
"minecraft": "${minecraft_range}",
|
||||
"java": ">=${java_version}"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "dev.photosync.platform.mixin",
|
||||
"compatibilityLevel": "${mixin_compat}",
|
||||
"client": [
|
||||
"CaptureMixin",
|
||||
"HudMixin",
|
||||
"QuitMixin"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user