Skip to content

Commit 71ea3ac

Browse files
committed
Rewrite books in Java
1 parent e326d0b commit 71ea3ac

File tree

13 files changed

+273
-256
lines changed

13 files changed

+273
-256
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package juuxel.adorn.client.book;
2+
3+
import com.mojang.serialization.Codec;
4+
import com.mojang.serialization.codecs.RecordCodecBuilder;
5+
import juuxel.adorn.util.MoreCodecs;
6+
import net.minecraft.text.Text;
7+
8+
import java.util.List;
9+
10+
public record Book(Text title, Text subtitle, Text author, List<Page> pages, float titleScale) {
11+
public static final Codec<Book> CODEC = RecordCodecBuilder.create(instance -> instance.group(
12+
MoreCodecs.TEXT.fieldOf("title").forGetter(Book::title),
13+
MoreCodecs.TEXT.fieldOf("subtitle").forGetter(Book::subtitle),
14+
MoreCodecs.TEXT.fieldOf("author").forGetter(Book::author),
15+
Page.CODEC.listOf().fieldOf("pages").forGetter(Book::pages),
16+
Codec.FLOAT.fieldOf("titleScale").forGetter(Book::titleScale)
17+
).apply(instance, Book::new));
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package juuxel.adorn.client.book;
2+
3+
import com.google.common.base.Predicates;
4+
import com.google.gson.Gson;
5+
import com.google.gson.JsonElement;
6+
import com.mojang.datafixers.util.Pair;
7+
import com.mojang.serialization.JsonOps;
8+
import juuxel.adorn.util.Logging;
9+
import net.minecraft.resource.JsonDataLoader;
10+
import net.minecraft.resource.ResourceManager;
11+
import net.minecraft.util.Identifier;
12+
import net.minecraft.util.profiler.Profiler;
13+
import org.slf4j.Logger;
14+
15+
import java.util.Map;
16+
import java.util.stream.Collectors;
17+
18+
public class BookManager extends JsonDataLoader {
19+
private static final Logger LOGGER = Logging.logger();
20+
private static final String DATA_TYPE = "adorn/books";
21+
private static final Gson GSON = new Gson();
22+
23+
private Map<Identifier, Book> books = Map.of();
24+
25+
public BookManager() {
26+
super(GSON, DATA_TYPE);
27+
}
28+
29+
@Override
30+
protected void apply(Map<Identifier, JsonElement> prepared, ResourceManager manager, Profiler profiler) {
31+
books = prepared.entrySet()
32+
.stream()
33+
.map(entry -> {
34+
var id = entry.getKey();
35+
var book = Book.CODEC.decode(JsonOps.INSTANCE, entry.getValue()).get();
36+
return book.map(
37+
pair -> Pair.of(id, pair.getFirst()),
38+
partial -> {
39+
LOGGER.error("Could not load book {}: {}", id, partial.message());
40+
return null;
41+
}
42+
);
43+
})
44+
.filter(Predicates.notNull())
45+
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
46+
}
47+
48+
public boolean contains(Identifier id) {
49+
return books.containsKey(id);
50+
}
51+
52+
public Book get(Identifier id) {
53+
var book = books.get(id);
54+
if (book == null) {
55+
throw new IllegalArgumentException("Tried to get unknown book '%s' from BookManager".formatted(id));
56+
}
57+
return book;
58+
}
59+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package juuxel.adorn.client.book;
2+
3+
import com.mojang.datafixers.util.Pair;
4+
import com.mojang.serialization.Codec;
5+
import com.mojang.serialization.codecs.RecordCodecBuilder;
6+
import juuxel.adorn.util.MoreCodecs;
7+
import juuxel.adorn.util.Vec2i;
8+
import net.minecraft.text.Text;
9+
import net.minecraft.util.Identifier;
10+
11+
import java.util.Arrays;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.stream.Collectors;
15+
16+
public record Image(Identifier location, Vec2i size, Placement placement, List<HoverArea> hoverAreas) {
17+
public static final Codec<Image> CODEC = RecordCodecBuilder.create(instance -> instance.group(
18+
Identifier.CODEC.fieldOf("location").forGetter(Image::location),
19+
Vec2i.CODEC.fieldOf("size").forGetter(Image::size),
20+
Placement.CODEC.optionalFieldOf("placement", Placement.AFTER_TEXT).forGetter(Image::placement),
21+
HoverArea.CODEC.listOf().optionalFieldOf("hoverAreas", List.of()).forGetter(Image::hoverAreas)
22+
).apply(instance, Image::new));
23+
24+
public enum Placement {
25+
BEFORE_TEXT("beforeText"),
26+
AFTER_TEXT("afterText");
27+
28+
private static final Map<String, Placement> BY_ID = Arrays.stream(values())
29+
.map(placement -> Pair.of(placement.id, placement))
30+
.collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
31+
public static final Codec<Placement> CODEC = Codec.STRING.xmap(BY_ID::get, placement -> placement.id);
32+
33+
private final String id;
34+
35+
Placement(String id) {
36+
this.id = id;
37+
}
38+
}
39+
40+
public record HoverArea(Vec2i position, Vec2i size, Text tooltip) {
41+
public static final Codec<HoverArea> CODEC = RecordCodecBuilder.create(instance -> instance.group(
42+
Vec2i.CODEC.fieldOf("position").forGetter(HoverArea::position),
43+
Vec2i.CODEC.fieldOf("size").forGetter(HoverArea::size),
44+
MoreCodecs.TEXT.fieldOf("tooltip").forGetter(HoverArea::tooltip)
45+
).apply(instance, HoverArea::new));
46+
47+
public boolean contains(int x, int y) {
48+
return position.x() <= x && x <= position.x() + size.x() && position.y() <= y && y <= position.y() + size.y();
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package juuxel.adorn.client.book;
2+
3+
import com.mojang.datafixers.util.Pair;
4+
import com.mojang.serialization.Codec;
5+
import com.mojang.serialization.DataResult;
6+
import com.mojang.serialization.DynamicOps;
7+
import com.mojang.serialization.codecs.RecordCodecBuilder;
8+
import juuxel.adorn.util.MoreCodecs;
9+
import net.minecraft.item.Item;
10+
import net.minecraft.item.ItemStack;
11+
import net.minecraft.registry.Registries;
12+
import net.minecraft.registry.RegistryKeys;
13+
import net.minecraft.registry.tag.TagKey;
14+
import net.minecraft.text.Text;
15+
import net.minecraft.util.Identifier;
16+
import org.jetbrains.annotations.Nullable;
17+
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Optional;
21+
22+
public record Page(List<Icon> icons, Text title, Text text, @Nullable Image image) {
23+
public static final Codec<Page> CODEC = RecordCodecBuilder.create(instance -> instance.group(
24+
Icon.CODEC.listOf().fieldOf("icons").forGetter(Page::icons),
25+
MoreCodecs.TEXT.fieldOf("title").forGetter(Page::title),
26+
MoreCodecs.TEXT.optionalFieldOf("text", Text.empty()).forGetter(Page::text),
27+
Image.CODEC.optionalFieldOf("image").forGetter(page -> Optional.ofNullable(page.image))
28+
).apply(instance, Page::new));
29+
30+
// For DFU
31+
private Page(List<Icon> icons, Text title, Text text, Optional<Image> image) {
32+
this(icons, title, text, image.orElse(null));
33+
}
34+
35+
public sealed interface Icon {
36+
Codec<Icon> CODEC = new Codec<>() {
37+
@Override
38+
public <T> DataResult<T> encode(Icon input, DynamicOps<T> ops, T prefix) {
39+
var id = switch (input) {
40+
case ItemIcon(var item) -> Registries.ITEM.getId(item).toString();
41+
case TagIcon(var tag) -> "#" + tag.id();
42+
};
43+
return ops.mergeToPrimitive(prefix, ops.createString(id));
44+
}
45+
46+
@Override
47+
public <T> DataResult<Pair<Icon, T>> decode(DynamicOps<T> ops, T input) {
48+
return ops.getStringValue(input)
49+
.map(id -> {
50+
Icon icon;
51+
if (id.startsWith("#")) {
52+
icon = new TagIcon(TagKey.of(RegistryKeys.ITEM, new Identifier(id.substring(1))));
53+
} else {
54+
icon = new ItemIcon(Registries.ITEM.get(new Identifier(id)));
55+
}
56+
57+
return Pair.of(icon, ops.empty());
58+
});
59+
}
60+
};
61+
62+
List<ItemStack> createStacks();
63+
64+
record ItemIcon(Item item) implements Icon {
65+
@Override
66+
public List<ItemStack> createStacks() {
67+
return List.of(item.getDefaultStack());
68+
}
69+
}
70+
71+
record TagIcon(TagKey<Item> tag) implements Icon {
72+
@Override
73+
public List<ItemStack> createStacks() {
74+
var entries = Registries.ITEM.getOrCreateEntryList(tag);
75+
List<ItemStack> result = new ArrayList<>(entries.size());
76+
for (var entry : entries) {
77+
result.add(entry.value().getDefaultStack());
78+
}
79+
return result;
80+
}
81+
}
82+
}
83+
}

common/src/main/java/juuxel/adorn/client/gui/screen/GuideBookScreen.java

+20-20
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ protected void init() {
7575
// its mouse hover tooltip renders on top of all widgets.
7676
flipBook = addDrawableChild(new FlipBook(this::updatePageTurnButtons));
7777
flipBook.add(new TitlePage(pageX, pageY));
78-
for (var page : book.getPages()) {
78+
for (var page : book.pages()) {
7979
var panel = new Panel();
8080
panel.add(new BookPageTitle(pageX, pageY, page));
8181
var body = new BookPageBody(pageX, pageY + PAGE_TEXT_Y, page);
@@ -161,7 +161,7 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
161161
private final class TitlePage implements Element, Drawable {
162162
private final int x;
163163
private final int y;
164-
private final Text byAuthor = Text.translatable("book.byAuthor", book.getAuthor());
164+
private final Text byAuthor = Text.translatable("book.byAuthor", book.author());
165165
private boolean focused = false;
166166

167167
private TitlePage(int x, int y) {
@@ -175,11 +175,11 @@ public void render(DrawContext context, int mouseX, int mouseY, float delta) {
175175
var matrices = context.getMatrices();
176176
matrices.push();
177177
matrices.translate(cx, y + 7 + 25, 0.0);
178-
matrices.scale(book.getTitleScale(), book.getTitleScale(), 1.0f);
179-
context.drawText(textRenderer, book.getTitle(), -textRenderer.getWidth(book.getTitle()) / 2, 0, Colors.SCREEN_TEXT, false);
178+
matrices.scale(book.titleScale(), book.titleScale(), 1.0f);
179+
context.drawText(textRenderer, book.title(), -textRenderer.getWidth(book.title()) / 2, 0, Colors.SCREEN_TEXT, false);
180180
matrices.pop();
181181

182-
context.drawText(textRenderer, book.getSubtitle(), cx - textRenderer.getWidth(book.getSubtitle()) / 2, y + 45, Colors.SCREEN_TEXT, false);
182+
context.drawText(textRenderer, book.subtitle(), cx - textRenderer.getWidth(book.subtitle()) / 2, y + 45, Colors.SCREEN_TEXT, false);
183183
context.drawText(textRenderer, byAuthor, cx - textRenderer.getWidth(byAuthor) / 2, y + 60, Colors.SCREEN_TEXT, false);
184184
}
185185

@@ -206,8 +206,8 @@ private final class BookPageTitle implements Element, Drawable, TickingElement {
206206
private BookPageTitle(int x, int y, Page page) {
207207
this.x = x;
208208
this.y = y;
209-
this.wrappedTitleLines = textRenderer.wrapLines(page.getTitle().copy().styled(style -> style.withBold(true)), PAGE_TITLE_WIDTH);
210-
this.icons = CollectionsKt.interleave(page.getIcons().stream().map(Page.Icon::createStacks).toList());
209+
this.wrappedTitleLines = textRenderer.wrapLines(page.title().copy().styled(style -> style.withBold(true)), PAGE_TITLE_WIDTH);
210+
this.icons = CollectionsKt.interleave(page.icons().stream().map(Page.Icon::createStacks).toList());
211211
}
212212

213213
@Override
@@ -254,9 +254,9 @@ private BookPageBody(int x, int y, Page page) {
254254
this.x = x;
255255
this.y = y;
256256
this.page = page;
257-
this.wrappedBodyLines = textRenderer.wrapLines(page.getText(), PAGE_WIDTH - PAGE_TEXT_X);
257+
this.wrappedBodyLines = textRenderer.wrapLines(page.text(), PAGE_WIDTH - PAGE_TEXT_X);
258258
this.textHeight = wrappedBodyLines.size() * textRenderer.fontHeight;
259-
this.imageHeight = page.getImage() != null ? page.getImage().getSize().getY() + PAGE_IMAGE_GAP : 0;
259+
this.imageHeight = page.image() != null ? page.image().size().y() + PAGE_IMAGE_GAP : 0;
260260
this.height = Math.max(PAGE_BODY_HEIGHT, textHeight + imageHeight);
261261
}
262262

@@ -293,39 +293,39 @@ public boolean isMouseOver(double mouseX, double mouseY) {
293293

294294
@Override
295295
public void render(DrawContext context, int mouseX, int mouseY, float delta) {
296-
int textYOffset = page.getImage() != null && page.getImage().getPlacement() == Image.Placement.BEFORE_TEXT ? imageHeight : 0;
296+
int textYOffset = page.image() != null && page.image().placement() == Image.Placement.BEFORE_TEXT ? imageHeight : 0;
297297

298298
for (int i = 0; i < wrappedBodyLines.size(); i++) {
299299
var line = wrappedBodyLines.get(i);
300300
context.drawText(textRenderer, line, x + PAGE_TEXT_X, textYOffset + y + i * textRenderer.fontHeight, Colors.SCREEN_TEXT, false);
301301
}
302302

303-
if (page.getImage() != null) {
304-
renderImage(context, page.getImage(), mouseX, mouseY);
303+
if (page.image() != null) {
304+
renderImage(context, page.image(), mouseX, mouseY);
305305
}
306306

307307
var hoveredStyle = getTextStyleAt(mouseX, mouseY);
308308
Scissors.suspendScissors(() -> context.drawHoverEvent(textRenderer, hoveredStyle, mouseX, mouseY));
309309
}
310310

311311
private void renderImage(DrawContext context, Image image, int mouseX, int mouseY) {
312-
var imageX = x + (PAGE_WIDTH - image.getSize().getX()) / 2;
313-
var imageY = switch (image.getPlacement()) {
312+
var imageX = x + (PAGE_WIDTH - image.size().x()) / 2;
313+
var imageY = switch (image.placement()) {
314314
case BEFORE_TEXT -> y;
315315
case AFTER_TEXT -> y + textHeight + PAGE_IMAGE_GAP;
316316
};
317317

318318
RenderSystem.enableBlend();
319-
context.drawTexture(image.getLocation(), imageX, imageY, 0f, 0f, image.getSize().getX(), image.getSize().getY(), image.getSize().getX(), image.getSize().getY());
319+
context.drawTexture(image.location(), imageX, imageY, 0f, 0f, image.size().x(), image.size().y(), image.size().x(), image.size().y());
320320
RenderSystem.disableBlend();
321321

322-
for (var hoverArea : image.getHoverAreas()) {
322+
for (var hoverArea : image.hoverAreas()) {
323323
if (hoverArea.contains(mouseX - imageX, mouseY - imageY)) {
324-
var hX = imageX + hoverArea.getPosition().getX();
325-
var hY = imageY + hoverArea.getPosition().getY();
326-
context.fill(hX, hY, hX + hoverArea.getSize().getX(), hY + hoverArea.getSize().getY(), HOVER_AREA_HIGHLIGHT_COLOR);
324+
var hX = imageX + hoverArea.position().x();
325+
var hY = imageY + hoverArea.position().y();
326+
context.fill(hX, hY, hX + hoverArea.size().x(), hY + hoverArea.size().y(), HOVER_AREA_HIGHLIGHT_COLOR);
327327

328-
var wrappedTooltip = textRenderer.wrapLines(hoverArea.getTooltip(), PAGE_WIDTH);
328+
var wrappedTooltip = textRenderer.wrapLines(hoverArea.tooltip(), PAGE_WIDTH);
329329
Scissors.suspendScissors(() -> context.drawOrderedTooltip(textRenderer, wrappedTooltip, mouseX, mouseY));
330330
break;
331331
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package juuxel.adorn.util;
2+
3+
import com.mojang.datafixers.util.Pair;
4+
import com.mojang.serialization.Codec;
5+
import com.mojang.serialization.DataResult;
6+
import com.mojang.serialization.DynamicOps;
7+
8+
import java.util.stream.IntStream;
9+
10+
public record Vec2i(int x, int y) {
11+
public static final Codec<Vec2i> CODEC = new Codec<>() {
12+
@Override
13+
public <T> DataResult<T> encode(Vec2i input, DynamicOps<T> ops, T prefix) {
14+
return ops.mergeToPrimitive(prefix, ops.createIntList(IntStream.of(input.x, input.y)));
15+
}
16+
17+
@Override
18+
public <T> DataResult<Pair<Vec2i, T>> decode(DynamicOps<T> ops, T input) {
19+
return ops.getIntStream(input).flatMap(stream -> {
20+
var iter = stream.iterator();
21+
22+
if (!iter.hasNext()) return mismatchedComponentCountResult();
23+
int x = iter.nextInt();
24+
if (!iter.hasNext()) return mismatchedComponentCountResult();
25+
int y = iter.nextInt();
26+
if (iter.hasNext()) return mismatchedComponentCountResult();
27+
28+
return DataResult.success(Pair.of(new Vec2i(x, y), ops.empty()));
29+
});
30+
}
31+
32+
@Override
33+
public String toString() {
34+
return "Vec2i";
35+
}
36+
};
37+
38+
private static <T> DataResult<T> mismatchedComponentCountResult() {
39+
return DataResult.error(() -> "Vec2i must have exactly two int components");
40+
}
41+
}

common/src/main/kotlin/juuxel/adorn/client/book/Book.kt

-26
This file was deleted.

0 commit comments

Comments
 (0)