Allow specifying items, blocks or oredict names for images.

Kinda meh because it obviously won't work when viewed via another Markdown parser, but it's not too terrible, either. Better than introducing <!--[OC:...]--> or something like that...
This commit is contained in:
Florian Nücke 2015-04-08 12:17:06 +02:00
parent c65142a059
commit 61f2eb268d
2 changed files with 118 additions and 10 deletions

View File

@ -1,11 +1,15 @@
# Headline with more lines [with link](huehue) and *some* more # Headline with more lines [with link](huehue) and *some* more
This is some test text for the subset of Markdown supported by the planned ingame documentation system for OpenComputers. This is some test text for the subset of Markdown supported by the planned ingame documentation system for OpenComputers.
![opencomputers:transistor](../../textures/gui/button_power.png) ![This is a tooltip...](../../textures/gui/printer_ink.png)
*This* is *italic* text, ~~strikethrough~~ maybe abc-ter **some** text **in bold**. Is _this underlined_? Oh, no, _it's also italic!_ Well, this \*isn't bold*. *This* is *italic* text, ~~strikethrough~~ maybe abc-ter **some** text **in bold**. Is _this underlined_? Oh, no, _it's also italic!_ Well, this [a link](blah).
![This is rendered live.](oredict:oc:assembler)
## Smaller headline [also with *link* but this __one__ longer](huehue) ## Smaller headline [also with *link* but this __one__ longer](huehue)
![This is another tooltip.](item:OpenComputers:item@23)
![All the colors.](oredict:craftingPiston)
This is *italic This is *italic
over two* lines. But *this ... no *this is* **_bold italic_** *text*. over two* lines. But *this ... no *this is* **_bold italic_** *text*.

View File

@ -3,20 +3,31 @@ package li.cil.oc.util
import java.io.InputStream import java.io.InputStream
import javax.imageio.ImageIO import javax.imageio.ImageIO
import com.google.common.base.Strings
import li.cil.oc.Settings import li.cil.oc.Settings
import net.minecraft.block.Block
import net.minecraft.client.Minecraft import net.minecraft.client.Minecraft
import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.FontRenderer
import net.minecraft.client.renderer.OpenGlHelper
import net.minecraft.client.renderer.RenderHelper
import net.minecraft.client.renderer.entity.RenderItem
import net.minecraft.client.renderer.texture.AbstractTexture import net.minecraft.client.renderer.texture.AbstractTexture
import net.minecraft.client.renderer.texture.TextureUtil import net.minecraft.client.renderer.texture.TextureUtil
import net.minecraft.client.resources.IResourceManager import net.minecraft.client.resources.IResourceManager
import net.minecraft.item.Item
import net.minecraft.item.ItemStack
import net.minecraft.util.EnumChatFormatting import net.minecraft.util.EnumChatFormatting
import net.minecraft.util.ResourceLocation import net.minecraft.util.ResourceLocation
import net.minecraftforge.oredict.OreDictionary
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL12
import scala.annotation.tailrec import scala.annotation.tailrec
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable import scala.collection.mutable
import scala.util.matching.Regex import scala.util.matching.Regex
/** /**
* Primitive Markdown parser, only supports a very small subset. Used for * Primitive Markdown parser, only supports a very small subset. Used for
* parsing documentation into segments, to be displayed in a GUI somewhere. * parsing documentation into segments, to be displayed in a GUI somewhere.
@ -62,11 +73,12 @@ object PseudoMarkdown {
var currentX = 0 var currentX = 0
var currentY = 0 var currentY = 0
for (segment <- document) { for (segment <- document) {
val result = segment.render(x, y + currentY - yOffset, currentX, maxWidth, maxHeight - (currentY - yOffset), renderer, mouseX, mouseY) val result = segment.render(x, y + currentY - yOffset, currentX, maxWidth, y, maxHeight - (currentY - yOffset), renderer, mouseX, mouseY)
hovered = hovered.orElse(result) hovered = hovered.orElse(result)
currentY += segment.height(currentX, maxWidth, renderer) currentY += segment.height(currentX, maxWidth, renderer)
currentX = segment.width(currentX, maxWidth, renderer) currentX = segment.width(currentX, maxWidth, renderer)
} }
if (mouseX < x || mouseX > x + maxWidth || mouseY < y || mouseY > y + maxHeight) hovered = None
hovered.foreach(_.notifyHover()) hovered.foreach(_.notifyHover())
// Restore all the things. // Restore all the things.
@ -119,7 +131,7 @@ object PseudoMarkdown {
*/ */
def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = 0 def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = 0
def render(x: Int, y: Int, indent: Int, maxWidth: Int, maxHeight: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = None def render(x: Int, y: Int, indent: Int, maxWidth: Int, minY: Int, maxY: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = None
} }
trait InteractiveSegment extends Segment { trait InteractiveSegment extends Segment {
@ -185,7 +197,7 @@ object PseudoMarkdown {
currentX + (stringWidth(chars, renderer) * resolvedScale).toInt currentX + (stringWidth(chars, renderer) * resolvedScale).toInt
} }
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, maxHeight: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { override def render(x: Int, y: Int, indent: Int, maxWidth: Int, minY: Int, maxY: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
val fontScale = resolvedScale val fontScale = resolvedScale
var currentX = x + indent var currentX = x + indent
var currentY = y var currentY = y
@ -193,7 +205,7 @@ object PseudoMarkdown {
var numChars = maxChars(chars, maxWidth - indent, renderer) var numChars = maxChars(chars, maxWidth - indent, renderer)
val interactive = findInteractive() val interactive = findInteractive()
var hovered: Option[InteractiveSegment] = None var hovered: Option[InteractiveSegment] = None
while (chars.length > 0 && (currentY - y) < maxHeight) { while (chars.length > 0 && (currentY - y) < maxY) {
val part = chars.take(numChars) val part = chars.take(numChars)
hovered = hovered.orElse(interactive.fold(None: Option[InteractiveSegment])(_.checkHovered(mouseX, mouseY, currentX, currentY, (stringWidth(part, renderer) * fontScale).toInt, (lineHeight(renderer) * fontScale).toInt))) hovered = hovered.orElse(interactive.fold(None: Option[InteractiveSegment])(_.checkHovered(mouseX, mouseY, currentX, currentY, (stringWidth(part, renderer) * fontScale).toInt, (lineHeight(renderer) * fontScale).toInt)))
GL11.glPushMatrix() GL11.glPushMatrix()
@ -315,6 +327,67 @@ object PseudoMarkdown {
override def toString: String = s"{StrikethroughSegment: text = $text}" override def toString: String = s"{StrikethroughSegment: text = $text}"
} }
private class ItemStackSegment(val parent: Segment, val title: String, val stacks: Array[ItemStack]) extends InteractiveSegment {
final val renderWidth = 32
final val renderHeight = 32
final val cycleSpeed = 1000
override def tooltip: Option[String] = Option(title)
override def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = math.max(lineHeight(renderer), renderHeight + 10 - lineHeight(renderer))
override def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = maxWidth
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, minY: Int, maxY: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
val xOffset = (maxWidth - renderWidth) / 2
val yOffset = 4 + (if (indent > 0) lineHeight(renderer) else 0)
val maskLow = math.max(minY - y - yOffset - 1, 0)
val maskHigh = math.max(maskLow, math.min(renderHeight, maxY - 3))
val mc = Minecraft.getMinecraft
val index = (System.currentTimeMillis() % (cycleSpeed * stacks.length)).toInt / cycleSpeed
val stack = stacks(index)
GL11.glPushMatrix()
GL11.glTranslatef(x + xOffset, y + yOffset, 0)
GL11.glColor4f(0.1f, 0.1f, 0.1f, 1)
GL11.glTranslatef(0, 0, 400)
GL11.glDepthFunc(GL11.GL_LEQUAL)
GL11.glDisable(GL11.GL_TEXTURE_2D)
GL11.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE)
GL11.glEnable(GL11.GL_COLOR_MATERIAL)
GL11.glDepthMask(true)
GL11.glColorMask(false, false, false, false)
GL11.glBegin(GL11.GL_QUADS)
GL11.glVertex2f(0, maskLow)
GL11.glVertex2f(0, maskHigh)
GL11.glVertex2f(renderWidth, maskHigh)
GL11.glVertex2f(renderWidth, maskLow)
GL11.glEnd()
GL11.glTranslatef(0, 0, -400)
GL11.glDepthFunc(GL11.GL_GEQUAL)
GL11.glEnable(GL11.GL_TEXTURE_2D)
GL11.glDisable(GL11.GL_COLOR_MATERIAL)
GL11.glDepthMask(false)
GL11.glColorMask(true, true, true, true)
GL11.glColor4f(1, 1, 1, 1)
GL11.glScalef(renderWidth / 16, renderHeight / 16, renderWidth / 16)
GL11.glEnable(GL12.GL_RESCALE_NORMAL)
RenderHelper.enableGUIStandardItemLighting()
OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, 240, 240)
RenderItem.getInstance.renderItemAndEffectIntoGUI(renderer, mc.getTextureManager, stack, 0, 0)
RenderHelper.disableStandardItemLighting()
GL11.glPopMatrix()
GL11.glDepthFunc(GL11.GL_EQUAL)
checkHovered(mouseX, mouseY, x + xOffset, y + yOffset, renderWidth, renderHeight)
}
override def toString: String = s"{ItemStackSegment: stacks = $stacks}"
}
private class ImageSegment(val parent: Segment, val title: String, val url: String) extends InteractiveSegment { private class ImageSegment(val parent: Segment, val title: String, val url: String) extends InteractiveSegment {
val path = if (url.startsWith("/")) url else "doc/img/" + url val path = if (url.startsWith("/")) url else "doc/img/" + url
val location = new ResourceLocation(Settings.resourceDomain, path) val location = new ResourceLocation(Settings.resourceDomain, path)
@ -335,10 +408,10 @@ object PseudoMarkdown {
override def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = maxWidth override def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = maxWidth
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, maxHeight: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { override def render(x: Int, y: Int, indent: Int, maxWidth: Int, minY: Int, maxY: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
Minecraft.getMinecraft.getTextureManager.bindTexture(location)
val xOffset = (maxWidth - texture.width) / 2 val xOffset = (maxWidth - texture.width) / 2
val yOffset = 4 + (if (indent > 0) lineHeight(renderer) else 0) val yOffset = 4 + (if (indent > 0) lineHeight(renderer) else 0)
Minecraft.getMinecraft.getTextureManager.bindTexture(location)
GL11.glColor4f(1, 1, 1, 1) GL11.glColor4f(1, 1, 1, 1)
GL11.glBegin(GL11.GL_QUADS) GL11.glBegin(GL11.GL_QUADS)
GL11.glTexCoord2f(0, 0) GL11.glTexCoord2f(0, 0)
@ -402,7 +475,38 @@ object PseudoMarkdown {
private def StrikethroughSegment(s: Segment, m: Regex.Match) = new StrikethroughSegment(s, m.group(1)) private def StrikethroughSegment(s: Segment, m: Regex.Match) = new StrikethroughSegment(s, m.group(1))
private def ImageSegment(s: Segment, m: Regex.Match) = new ImageSegment(s, m.group(1), m.group(2)) private def ImageSegment(s: Segment, m: Regex.Match) = {
try {
if (m.group(2).startsWith("item:")) {
val desc = m.group(2).stripPrefix("item:")
val (name, optMeta) = desc.splitAt(desc.lastIndexOf('@'))
val meta = if (Strings.isNullOrEmpty(optMeta)) 0 else Integer.parseInt(optMeta.drop(1))
Item.itemRegistry.getObject(name) match {
case item: Item => new ItemStackSegment(s, m.group(1), Array(new ItemStack(item, 1, meta)))
case _ => new TextSegment(s, m.group(1))
}
}
else if (m.group(2).startsWith("block:")) {
val desc = m.group(2).stripPrefix("block:")
val (name, optMeta) = desc.splitAt(desc.lastIndexOf('@'))
val meta = if (Strings.isNullOrEmpty(optMeta)) 0 else Integer.parseInt(optMeta.drop(1))
Block.blockRegistry.getObject(name) match {
case block: Block => new ItemStackSegment(s, m.group(1), Array(new ItemStack(block, 1, meta)))
case _ => new TextSegment(s, m.group(1))
}
}
else if (m.group(2).startsWith("oredict:")) {
val name = m.group(2).stripPrefix("oredict:")
val stacks = OreDictionary.getOres(name)
if (stacks != null && stacks.nonEmpty) new ItemStackSegment(s, m.group(1), stacks.toArray(new Array[ItemStack](stacks.size())))
else new TextSegment(s, m.group(1))
}
else new ImageSegment(s, m.group(1), m.group(2))
}
catch {
case t: Throwable => new TextSegment(s, t.getMessage)
}
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //