/**
* Copyright (c) Lambda Innovation, 2013-2016
* This file is part of the AcademyCraft mod.
* https://github.com/LambdaInnovation/AcademyCraft
* Licensed under GPLv3, see project root for more information.
*/
package cn.academy.misc.tutorial.client;
import cn.academy.core.AcademyCraft;
import cn.academy.core.client.ACRenderingHelper;
import cn.academy.core.Resources;
import cn.academy.misc.tutorial.ACTutorial;
import cn.academy.misc.tutorial.TutorialRegistry;
import cn.academy.misc.tutorial.ViewGroup;
import cn.lambdalib.annoreg.core.Registrant;
import cn.lambdalib.annoreg.mc.RegInitCallback;
import cn.lambdalib.cgui.gui.CGuiScreen;
import cn.lambdalib.cgui.gui.Widget;
import cn.lambdalib.cgui.gui.WidgetContainer;
import cn.lambdalib.cgui.gui.component.*;
import cn.lambdalib.cgui.gui.component.Transform.HeightAlign;
import cn.lambdalib.cgui.gui.event.FrameEvent;
import cn.lambdalib.cgui.gui.event.LeftClickEvent;
import cn.lambdalib.cgui.xml.CGUIDocument;
import cn.lambdalib.util.client.HudUtils;
import cn.lambdalib.util.client.font.IFont;
import cn.lambdalib.util.client.font.IFont.FontOption;
import cn.lambdalib.util.generic.MathUtils;
import cn.lambdalib.util.helper.Color;
import cn.lambdalib.util.helper.GameTimer;
import cn.lambdalib.util.markdown.GLMarkdownRenderer;
import cn.lambdalib.util.markdown.MarkdownParser;
import com.google.common.base.Preconditions;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.ResourceLocation;
import org.apache.commons.lang3.tuple.Pair;
import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.lwjgl.opengl.GL11.*;
/**
* @author WeAthFolD
*/
@Registrant
@SideOnly(Side.CLIENT)
public class GuiTutorial extends CGuiScreen {
private static IFont font, fontBold, fontItalic;
private static WidgetContainer loaded;
@RegInitCallback
private static void __init() {
loaded = CGUIDocument.panicRead(new ResourceLocation("academy:guis/tutorial.xml"));
font = Resources.font();
fontBold = Resources.fontBold();
fontItalic = Resources.fontItalic();
}
private static final double REF_WIDTH = 480;
private final Color GLOW_COLOR = Color.white();
private final FontOption fo_descTitle = new FontOption(10);
private double cachedWidth = -1;
private final EntityPlayer player;
private final List<ACTutorial> learned, unlearned;
private final boolean firstOpen;
private Widget frame;
private Widget leftPart, rightPart;
private Widget listArea;
private Widget showWindow, rightWindow, centerPart;
private Widget logo0, logo1, logo2, logo3;
private Widget showArea, tagArea;
// Current displayed tutorial
private TutInfo currentTut = null;
private class CachedRenderInfo {
final String title, rawBrief, rawContent;
private GLMarkdownRenderer brief_;
private GLMarkdownRenderer content_;
CachedRenderInfo(String _title, String _brief, String _content) {
title = _title;
rawBrief = _brief;
rawContent = _content;
}
GLMarkdownRenderer getBrief() {
if (brief_ == null) {
GLMarkdownRenderer renderer = new ACMarkdownRenderer();
renderer.setFonts(font, fontBold, fontItalic);
renderer.widthLimit_$eq(130);
renderer.fontSize_$eq(8);
MarkdownParser.accept(rawBrief, renderer);
brief_ = renderer;
}
return brief_;
}
GLMarkdownRenderer getContent() {
if (content_ == null) {
GLMarkdownRenderer renderer = new ACMarkdownRenderer();
renderer.setFonts(font, fontBold, fontItalic);
renderer.widthLimit_$eq(150);
renderer.fontSize_$eq(8);
MarkdownParser.accept(rawContent, renderer);
content_ = renderer;
}
return content_;
}
}
private Map<ACTutorial, CachedRenderInfo> cached = new HashMap<>();
private CachedRenderInfo renderInfo(ACTutorial tut) {
if (!cached.containsKey(tut)) {
String raw = tut.getContent();
int i1 = raw.indexOf("![title]"),
i2 = raw.indexOf("![brief]"),
i3 = raw.indexOf("![content]");
if (i1 < i2 && i2 < i3 && i1 != -1) {
String title = trimHead(raw.substring(i1+8, i2)),
brief = trimHead(raw.substring(i2+8, i3)),
content = trimHead(raw.substring(i3+10));
cached.put(tut, new CachedRenderInfo(title, brief, content));
} else {
throw new RuntimeException("Malformed tutorial " + tut.id);
}
}
return cached.get(tut);
}
private String trimHead(String str) {
int idx = 0;
while (idx < str.length() &&
(str.charAt(idx) == '\r' || str.charAt(idx) == '\n' || str.charAt(idx) == ' ')) {
idx++;
}
return str.substring(idx);
}
public GuiTutorial() {
player = Minecraft.getMinecraft().thePlayer;
Pair<List<ACTutorial>, List<ACTutorial>> p = TutorialRegistry.groupByLearned(player);
learned = p.getLeft();
unlearned = p.getRight();
final String tagName = "AC_Tutorial_Open";
firstOpen = !player.getEntityData().getBoolean(tagName);
player.getEntityData().setBoolean(tagName, true);
initUI();
}
@Override
public void drawScreen(int mx, int my, float w) {
// Make the whole screen scale with width, for better display effect
if(cachedWidth != width) {
frame.transform.scale = width / REF_WIDTH;
frame.dirty = true;
}
cachedWidth = width;
super.drawScreen(mx, my, w);
}
private void initUI() {
frame = loaded.getWidget("frame").copy();
leftPart = frame.getWidget("leftPart");
listArea = leftPart.getWidget("list");
rightPart = frame.getWidget("rightPart");
showWindow = rightPart.getWidget("showWindow");
rightWindow = rightPart.getWidget("rightWindow");
centerPart = rightPart.getWidget("centerPart");
logo0 = rightPart.getWidget("logo0");
logo1 = rightPart.getWidget("logo1");
logo2 = rightPart.getWidget("logo2");
logo3 = rightPart.getWidget("logo3");
showArea = showWindow.getWidget("area");
tagArea = showWindow.getWidget("tag_area");
showWindow.transform.doesDraw = false;
rightWindow.transform.doesDraw = false;
centerPart.transform.doesDraw = false;
// Event handlers
centerPart.getWidget("text").listen(FrameEvent.class, (w, e) -> {
if(currentTut != null) {
GLMarkdownRenderer renderer = renderInfo(currentTut.tut).getContent();
glPushMatrix();
glTranslated(0, 0, 10);
glColorMask(false, false, false, false);
glDepthMask(true);
HudUtils.colorRect(0, 0, w.transform.width, w.transform.height);
glColorMask(true, true, true, true);
double ht = Math.max(0, renderer.getMaxHeight() - w.transform.height + 10);
double delta = VerticalDragBar.get(centerPart.getWidget("scroll_2")).getProgress() * ht;
glTranslated(3, 3 - delta, 0);
glDepthFunc(GL_EQUAL);
renderer.render();
glDepthFunc(GL_LEQUAL);
glPopMatrix();
}
});
rightWindow.getWidget("text").listen(FrameEvent.class, (w, e) -> {
if(currentTut != null) {
CachedRenderInfo info = renderInfo(currentTut.tut);
font.draw(info.title, 3, 3, fo_descTitle);
glPushMatrix();
glTranslated(3, 15, 0);
info.getBrief().render();
glPopMatrix();
}
});
showArea.listen(FrameEvent.class, (w, e) -> {
final Widget view = currentView();
if (view == null) {
return;
}
glMatrixMode(GL11.GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
double scale = 366.0 / width * frame.scale;
float aspect = (float) mc.displayWidth / mc.displayHeight;
glTranslated(
-1 + 2.0 * (w.scale + w.x) / width,
1 - 2.0 * (w.scale + w.y) / height,
0);
GL11.glScaled(scale, -scale * aspect, -0.5);
GLU.gluPerspective(50, 1, 1f, 100);
glMatrixMode(GL11.GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// glCullFace(GL_FRONT);
// glDisable(GL11.GL_DEPTH_TEST);
glDisable(GL11.GL_ALPHA_TEST);
glEnable(GL11.GL_BLEND);
glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
glCullFace(GL_FRONT);
glColor4d(1, 1, 1, 1);
glTranslated(0, 0, -4);
glTranslated(.55, .55, .5);
glScaled(.75, -.75, .75);
glRotated(-20, 1, 0, 0.1);
view.post(new ViewRenderEvent());
glPopMatrix();
glMatrixMode(GL11.GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL11.GL_MODELVIEW);
glEnable(GL11.GL_DEPTH_TEST);
glEnable(GL11.GL_ALPHA_TEST);
glCullFace(GL11.GL_BACK);
});
// Left and right button of the preview view.
// It is assumed when event is triggered, current preview is present and can be switched.
showWindow.getWidget("btn_left").listen(LeftClickEvent.class, (w, e) -> {
PreviewInfo info = Preconditions.checkNotNull(currentPreview());
info.viewIndex -= 1;
if (info.viewIndex < 0) {
info.viewIndex = info.subViews.length - 1;
}
updateView();
});
showWindow.getWidget("btn_right").listen(LeftClickEvent.class, (w, e) -> {
PreviewInfo info = Preconditions.checkNotNull(currentPreview());
info.viewIndex = (info.viewIndex + 1) % info.subViews.length;
updateView();
});
{
FontOption option = new FontOption(10);
tagArea.listen(FrameEvent.class, (w, evt) -> {
Widget hovering = gui.getHoveringWidget();
if (hovering != null) {
ViewGroupButton comp = hovering.getComponent(ViewGroupButton.class);
if (comp != null) {
font.draw(comp.group.getDisplayText(), 0, -8, option);
}
}
});
}
//
rebuildList();
final double ln = 500, ln2 = 300, cl = 50;
final float ht = 5;
if (!firstOpen) {
logo1.listen(FrameEvent.class, (w, e) -> {
glPushMatrix();
glTranslated(logo1.transform.width / 2, logo1.transform.height / 2 + 15, 0);
lineglow(ln - ln2, ln, ht);
lineglow(-ln, -(ln - ln2), ht);
glPopMatrix();
});
} else {
listArea.transform.doesDraw = false;
/* Start animation controller */ {
blend(logo2, 0.65, 0.3);
blend(logo0, 1.75, 0.3);
blend(leftPart, 1.75, 0.3);
blend(logo1, 1.3, 0.3);
blend(logo3, 0.1, 0.3);
blendy(logo3, 0.7, 0.4, 63, -36);
long startTime = GameTimer.getAbsTime();
logo1.listen(FrameEvent.class, (__, e) -> {
final double
b1 = 0.3, // Blend stage 1
b2 = 0.2; // Blend stage 2
glPushMatrix();
glTranslated(logo1.transform.width / 2, logo1.transform.height / 2 + 15, 0);
double dt = (GameTimer.getAbsTime() - startTime) / 1000.0 - 0.4;
if(dt < 0) dt = 0;
if(dt < b1) {
if(dt > 0) {
double len = MathUtils.lerp(0, ln, dt / b1);
if(len > cl) {
lineglow(cl, len, ht);
lineglow(-len, -cl, ht);
}
}
} else {
double ldt = dt - b1;
if(ldt > b2) {
ldt = b2;
}
double len = ln;
double len2 = MathUtils.lerp(ln - 2 * cl, ln2, ldt / b2);
lineglow(ln - len2, len, ht);
lineglow(-len, -(ln - len2), ht);
}
glPopMatrix();
listArea.transform.doesDraw = dt > 2.0;
});
}
}
gui.addWidget("frame", frame);
}
private void _build(ElementList e1, List<ACTutorial> list, boolean learned) {
for(ACTutorial t : list) {
Widget w = new Widget();
w.transform.setSize(72, 12);
w.addComponent(new Tint(Color.whiteBlend(0.0), Color.whiteBlend(0.3)));
TextBox box = Resources.newTextBox(new FontOption(10, learned ? Color.white() : Color.mono(0.6)));
box.xOffset = 3;
box.content = renderInfo(t).title;
box.localized = true;
box.emit = true;
box.heightAlign = HeightAlign.CENTER;
w.listen(LeftClickEvent.class, (__, e) ->
{
if(currentTut == null) {
// Start blending view area!
for(Widget old : new Widget[] { logo2, logo0, logo1, logo3 }) {
blend(old, 0, 0.3, true);
}
centerPart.transform.doesDraw = learned;
rightWindow.transform.doesDraw = true;
showWindow.transform.doesDraw = true;
}
if (currentTut == null || currentTut.tut != t) {
updateTutorial(t);
}
});
w.addComponent(box);
e1.addWidget(w);
}
}
private void rebuildList() {
listArea.removeComponent("ElementList");
ElementList el = new ElementList();
_build(el, learned, true);
_build(el, unlearned, false);
listArea.addComponent(el);
}
private void lineglow(double x0, double x1, float ht) {
ACRenderingHelper.drawGlow(x0, -1, x1-x0, ht-2, 5, GLOW_COLOR);
glColor4d(1, 1, 1, 1);
ACRenderingHelper.lineSegment(x0, 0, x1, 0, ht);
}
private void blend(Widget w, double start, double tin) {
blend(w, start, tin, false);
}
private void blend(Widget w, double start, double tin, boolean reverse) {
DrawTexture dt = DrawTexture.get(w);
long startTime = GameTimer.getAbsTime();
double startAlpha = dt.color.a;
dt.color.a = reverse ? startAlpha : 0;
w.listen(FrameEvent.class, (__, e) ->
{
double delta = (GameTimer.getAbsTime() - startTime) / 1000.0;
double alpha = startAlpha *
MathUtils.clampd(0, 1, delta < start ? 0 : (delta - start < tin ? (delta - start ) / tin : 1));
if(reverse) {
alpha = 1 - alpha;
if(alpha == 0) {
w.dispose();
}
}
dt.color.a = alpha;
});
}
private void blendy(Widget w, double start, double tin, double y0, double y1) {
long startTime = GameTimer.getAbsTime();
w.transform.y = y0;
w.dirty = true;
w.listen(FrameEvent.class, (__, e) ->
{
double delta = (GameTimer.getAbsTime() - startTime) / 1000.0;
double lambda = delta < start ? 0 : (delta - start < tin ? (delta - start ) / tin : 1);
w.transform.y = MathUtils.lerp(y0, y1, lambda);
w.dirty = true;
});
}
private void updateTutorial(ACTutorial tut) {
currentTut = new TutInfo(tut, tut.isActivated(player));
VerticalDragBar.get(centerPart.getWidget("scroll_2")).setProgress(0.0);
centerPart.transform.doesDraw = tut.isActivated(player);
tagArea.clear();
{
double sz = tagArea.transform.height;
double step = sz - 1;
double x = 0;
for (int i = 0; i < currentTut.previews.length; ++i) {
final int i2 = i;
ViewGroup h = currentTut.previews[i].handler;
Widget w = new Widget()
.size(sz, sz)
.pos(x, 0)
.addComponent(new ViewGroupButton(h))
.addComponent(new DrawTexture(h.getTag().icon, Color.monoBlend(1, .7)))
.addComponent(new Tint(Color.monoBlend(1, .7), Color.monoBlend(1, 1)).setAffectTexture())
.listen(LeftClickEvent.class, (w_, e) -> {
currentTut.previewIndex = i2;
updatePreview();
});
tagArea.addWidget(w);
x += step;
}
}
updatePreview();
}
private PreviewInfo currentPreview() {
return currentTut == null ? null : currentTut.currentPreview();
}
private Widget currentView() {
PreviewInfo cp = currentPreview();
return cp == null ? null : cp.currentView();
}
private void updateView() {
showArea.removeWidget("delegate");
Widget view = currentView();
if (view != null) {
showArea.addWidget("delegate", view);
}
}
private void updatePreview() {
PreviewInfo current = currentPreview();
Widget btn_left = showWindow.getWidget("btn_left");
Widget btn_right = showWindow.getWidget("btn_right");
boolean hides = current == null || current.subViews.length < 2;
btn_left.transform.doesDraw = !hides;
btn_right.transform.doesDraw = !hides;
updateView();
}
private class TutInfo {
final ACTutorial tut;
final boolean learned;
final PreviewInfo[] previews;
int previewIndex;
TutInfo(ACTutorial _tut, boolean _learned) {
tut = _tut;
learned = _learned;
previews = tut.getPreview().stream()
.map(PreviewInfo::new)
.toArray(PreviewInfo[]::new);
}
public PreviewInfo currentPreview() {
return previews.length == 0 ? null : previews[previewIndex];
}
}
private class PreviewInfo {
final ViewGroup handler;
final Widget[] subViews;
int viewIndex;
public PreviewInfo(ViewGroup handler) {
this.handler = handler;
subViews = handler.getSubViews();
}
public Widget currentView() {
return subViews.length == 0 ? null : subViews[viewIndex];
}
}
private class ViewGroupButton extends Component {
public final ViewGroup group;
public ViewGroupButton(ViewGroup _group) {
super("VGB");
group = _group;
}
}
private void debug(Object msg) {
AcademyCraft.log.info("[Tut] " + msg);
}
}