Skip to content
Open
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
1 change: 1 addition & 0 deletions common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {

implementation 'org.commonmark:commonmark:0.27.1'
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.27.1'
implementation 'org.commonmark:commonmark-ext-gfm-tables:0.27.1'

}

Expand Down
2 changes: 2 additions & 0 deletions common/src/main/java/rearth/oracle/OracleClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import rearth.oracle.tooltip.DocumentationTooltipHandler;
import rearth.oracle.ui.OracleScreen;
import rearth.oracle.ui.SearchScreen;
import rearth.oracle.util.AudioPlayer;
import rearth.oracle.util.TitleLookup;

import java.util.*;
Expand Down Expand Up @@ -79,6 +80,7 @@ public static void init() {

ReloadListenerRegistry.register(PackType.CLIENT_RESOURCES, (ResourceManagerReloadListener) manager -> {
Oracle.LOGGER.info("Indexing Oracle Wiki Resources...");
AudioPlayer.release();
findAllResourceEntries(manager);
getOrCreateSearch(); // start search to begin indexing in advance
}, Identifier.fromNamespaceAndPath(Oracle.MOD_ID, "wiki_resources"));
Expand Down
7 changes: 7 additions & 0 deletions common/src/main/java/rearth/oracle/ui/WikiBaseScreen.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.Nullable;
import rearth.oracle.ui.widgets.UIComponent;
import rearth.oracle.util.AudioPlayer;

import java.util.ArrayList;
import java.util.List;
Expand Down Expand Up @@ -96,6 +97,12 @@ private UIComponent topmostAt(double mouseX, double mouseY) {
return null;
}

@Override
public void removed() {
super.removed();
AudioPlayer.stop();
}

@Override
public void tick() {
super.tick();
Expand Down
59 changes: 59 additions & 0 deletions common/src/main/java/rearth/oracle/ui/widgets/AudioWidget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package rearth.oracle.ui.widgets;

import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.Identifier;
import rearth.oracle.util.AudioPlayer;

public class AudioWidget extends FlowWidget {
private static final String CHAR_PLAY = "▶";
private static final String CHAR_STOP = "■";
private static final int BUTTON_SIZE = 16;

private final Identifier location;
private final LabelWidget glyph;
private boolean wasPlaying;

public AudioWidget(Identifier location, String displayName) {
super(Direction.HORIZONTAL);
this.location = location;
this.glyph = new LabelWidget(glyphText(false));

setSurface(WikiSurface.BEDROCK_PANEL_DARK);
setPadding(Insets.of(5, 7));
gap(5);
verticalAlignment(VerticalAlignment.CENTER);

ClickableWidget button = new ClickableWidget(glyph, b -> {
AudioPlayer.toggle(location);
refreshGlyph();
})
.fixedSize(BUTTON_SIZE, BUTTON_SIZE)
.centerChild()
.surfaces(WikiSurface.BEDROCK_PANEL, WikiSurface.BEDROCK_PANEL_HOVER,
WikiSurface.BEDROCK_PANEL_PRESSED, WikiSurface.BEDROCK_PANEL, WikiSurface.BEDROCK_PANEL_DISABLED);
button.setPadding(new Insets(1, 0, 0, 2));

super.child(button);
super.child(new LabelWidget(Component.literal(displayName).withStyle(ChatFormatting.GRAY)));
}

private static Component glyphText(boolean playing) {
return Component.literal(playing ? CHAR_STOP : CHAR_PLAY).withStyle(ChatFormatting.DARK_GRAY);
}

private void refreshGlyph() {
boolean playing = AudioPlayer.isPlaying(location);
if (playing == wasPlaying) return;

wasPlaying = playing;
glyph.text(glyphText(playing));
}

@Override
protected void renderContent(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
refreshGlyph();
super.renderContent(context, mouseX, mouseY, delta);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package rearth.oracle.ui.widgets;

import net.minecraft.client.gui.GuiGraphicsExtractor;

public class BlockQuoteWidget extends FlowWidget {
private static final int RULE_COLOR = 0x80777777;
private static final int RULE_WIDTH = 2;

public BlockQuoteWidget() {
super(Direction.VERTICAL);

gap(2);
setPadding(Insets.of(2, 0, 2, 10));
}

@Override
protected void renderContent(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
context.fill(x + 2, y, x + 2 + RULE_WIDTH, y + height, RULE_COLOR);
super.renderContent(context, mouseX, mouseY, delta);
}
}
122 changes: 92 additions & 30 deletions common/src/main/java/rearth/oracle/ui/widgets/CalloutWidget.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package rearth.oracle.ui.widgets;

import net.minecraft.ChatFormatting;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.resources.sounds.SimpleSoundInstance;
import net.minecraft.network.chat.Component;
import net.minecraft.ChatFormatting;
import net.minecraft.sounds.SoundEvents;
import org.jetbrains.annotations.Nullable;
import rearth.oracle.util.CalloutVariant;

import java.util.Locale;

/**
* Stylised callout block used by {@code <Callout>} markdown tags.
* Renders a panel for the body content with a small overlapping title chip
Expand All @@ -16,74 +17,135 @@
*/
public class CalloutWidget extends FlowWidget {
private static final int BODY_TEXT_COLOR = 0xFF555555;

private static final int LABEL_OVERLAP = 6;
private static final int LABEL_PAD_X = 12;
private static final int LABEL_PAD_Y = 9;

private static final String CH_CLOSED = " >";
private static final String CH_OPEN = " v";

private final CalloutVariant variant;
private final Component title;
private final boolean collapsible;
private final FlowWidget body;

@SuppressWarnings("this-escape")
public CalloutWidget(CalloutVariant variant) {

private boolean expanded;
private int labelX, labelY, labelWidth, labelHeight;

public CalloutWidget(CalloutVariant variant, @Nullable Component title, boolean collapsible, boolean collapsed) {
super(Direction.VERTICAL);
this.variant = variant;
this.title = title != null ? title : variant.getTitle();
this.collapsible = collapsible;
this.expanded = !collapsed;
this.body = FlowWidget.vertical();
body.setSurface(WikiSurface.BEDROCK_PANEL);
body.setPadding(Insets.of(14, 8, 10, 10)); // extra top so the chip doesn't overlap the text
body.setVisible(expanded);
super.child(body);
}

public CalloutWidget addBodyChild(UIComponent child) {
tintBodyText(child);
body.child(child);
return this;
}

private void tintBodyText(UIComponent child) {
if (child instanceof LabelWidget label) {
label.color(BODY_TEXT_COLOR);
} else if (child instanceof FlowWidget flow) {
for (var nested : flow.children()) tintBodyText(nested);
} else if (child instanceof TableWidget table) {
table.color(BODY_TEXT_COLOR);
} else if (child instanceof FlowWidget flow && flow.getSurface().isNone()) {
for (var nested : flow.children()) {
tintBodyText(nested);
}
}
}


public boolean isExpanded() {
return expanded;
}

public void setExpanded(boolean expanded) {
if (this.expanded == expanded) return;
this.expanded = expanded;
body.setVisible(expanded);
requestLayout();
}

private Component getLabelTitle() {
var text = title.copy().withStyle(ChatFormatting.WHITE);
if (collapsible) text.append(Component.literal(expanded ? CH_OPEN : CH_CLOSED));
return text;
}

private int getLabelHeight() {
return Minecraft.getInstance().font.lineHeight + LABEL_PAD_Y;
}

@Override
public int getPreferredWidth(int widthHint) {
if (widthHint > 0) return widthHint;
return super.getPreferredWidth(widthHint);
}

@Override
public int getPreferredHeight(int widthHint) {
if (!expanded) return getLabelHeight();
if (widthHint > 0) return body.getPreferredHeight(calloutWidth(widthHint));
return super.getPreferredHeight(widthHint);
}

@Override
public void layout(int parentWidthHint, int parentHeightHint) {
width = parentWidthHint > 0 ? parentWidthHint : getPreferredWidth(-1);
int bodyWidth = calloutWidth(width);
int bodyHeight = body.getPreferredHeight(bodyWidth);
height = bodyHeight;
body.setPosition(x + (width - bodyWidth) / 2, y);
body.setLayoutSize(bodyWidth, bodyHeight);
body.layout(bodyWidth, bodyHeight);
int bodyX = x + (width - bodyWidth) / 2;

if (expanded) {
int bodyHeight = body.getPreferredHeight(bodyWidth);
height = bodyHeight;
body.setPosition(bodyX, y);
body.setLayoutSize(bodyWidth, bodyHeight);
body.layout(bodyWidth, bodyHeight);
} else {
height = getLabelHeight();
body.setPosition(bodyX, y + LABEL_OVERLAP);
body.setLayoutSize(bodyWidth, 0);
}

var tr = Minecraft.getInstance().font;
labelWidth = tr.width(getLabelTitle()) + LABEL_PAD_X;
labelHeight = getLabelHeight();
labelX = bodyX - LABEL_OVERLAP;
labelY = body.getY() - LABEL_OVERLAP;
}

private int calloutWidth(int availableWidth) {
return Math.min(Math.max(1, availableWidth), Math.max(120, (int) (availableWidth * 0.8f)));
}

@Override
protected void renderContent(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
super.renderContent(context, mouseX, mouseY, delta);
// overlapping title chip rendered on top
var tr = Minecraft.getInstance().font;
var key = "oracle_index.callout." + this.variant.name().toLowerCase(Locale.ROOT);
var title = Component.translatable(key).withStyle(ChatFormatting.WHITE);
int textW = tr.width(title);
int chipW = textW + 12;
int chipH = tr.lineHeight + 9;
int chipX = body.getX();
int chipY = body.getY();
this.variant.getSurface().render(context, chipX - 6, chipY - 6, chipW, chipH);
context.text(tr, title, chipX, chipY, 0xFFFFFFFF, false);
this.variant.getSurface().render(context, labelX, labelY, labelWidth, labelHeight);
context.text(tr, getLabelTitle(), labelX + LABEL_OVERLAP, labelY + LABEL_OVERLAP, 0xFFFFFFFF, false);
}

@Override
public boolean handleClick(double mouseX, double mouseY, int button) {
if (collapsible && button == 0 && isOverLabel(mouseX, mouseY)) {
setExpanded(!expanded);
Minecraft.getInstance().getSoundManager().play(SimpleSoundInstance.forUI(SoundEvents.UI_BUTTON_CLICK, 1.0f));
return true;
}
return super.handleClick(mouseX, mouseY, button);
}

private boolean isOverLabel(double mouseX, double mouseY) {
return mouseX >= labelX && mouseX < labelX + labelWidth && mouseY >= labelY && mouseY < labelY + labelHeight;
}
}
50 changes: 50 additions & 0 deletions common/src/main/java/rearth/oracle/ui/widgets/CodeBlockWidget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package rearth.oracle.ui.widgets;

import net.minecraft.ChatFormatting;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import org.jetbrains.annotations.Nullable;

public class CodeBlockWidget extends FlowWidget {
private static final int CODE_COLOR = 0xFFB0B4BC;
private static final int DIVIDER_COLOR = 0x40FFFFFF;

@Nullable
private final LabelWidget header;

public CodeBlockWidget(@Nullable String fileName, @Nullable String language, String code) {
super(Direction.VERTICAL);
setSurface(WikiSurface.BEDROCK_PANEL_DARK);
setPadding(Insets.of(6));
gap(4);

MutableComponent headerText = headerText(fileName, language);
this.header = headerText == null ? null : new LabelWidget(headerText);
if (header != null) super.child(header);

super.child(new LabelWidget(Component.literal(code.stripTrailing())).color(CODE_COLOR).lineSpacing(1));
}

@Nullable
private static MutableComponent headerText(@Nullable String fileName, @Nullable String language) {
boolean hasFile = fileName != null && !fileName.isBlank();
boolean hasLanguage = language != null && !language.isBlank();
if (!hasFile && !hasLanguage) return null;

if (!hasFile) {
return Component.literal(language).withStyle(ChatFormatting.GRAY);
}

return Component.literal(fileName).withStyle(ChatFormatting.GOLD);
}

@Override
protected void renderContent(GuiGraphicsExtractor context, int mouseX, int mouseY, float delta) {
super.renderContent(context, mouseX, mouseY, delta);
if (header == null) return;

int lineY = header.getY() + header.getHeight() + 1;
context.fill(x + padding.left(), lineY, x + width - padding.right(), lineY + 1, DIVIDER_COLOR);
}
}
Loading
Loading