mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-19 12:17:17 -04:00
Initial work on ingame documentation via a manual / book.
Very, *very* basic Markdown parser and renderer working.
This commit is contained in:
parent
2d778f20fa
commit
ca8f1e3b94
BIN
assets/items.psd
BIN
assets/items.psd
Binary file not shown.
BIN
src/main/resources/assets/opencomputers/textures/gui/manual.png
Normal file
BIN
src/main/resources/assets/opencomputers/textures/gui/manual.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 447 B |
Binary file not shown.
After Width: | Height: | Size: 432 B |
@ -102,6 +102,7 @@ object Constants {
|
|||||||
final val LinkedCard = "linkedCard"
|
final val LinkedCard = "linkedCard"
|
||||||
final val LootDisk = "lootDisk"
|
final val LootDisk = "lootDisk"
|
||||||
final val LuaBios = "luaBios"
|
final val LuaBios = "luaBios"
|
||||||
|
final val Manual = "manual"
|
||||||
final val MicrocontrollerCaseCreative = "microcontrollerCaseCreative"
|
final val MicrocontrollerCaseCreative = "microcontrollerCaseCreative"
|
||||||
final val MicrocontrollerCaseTier1 = "microcontrollerCase1"
|
final val MicrocontrollerCaseTier1 = "microcontrollerCase1"
|
||||||
final val MicrocontrollerCaseTier2 = "microcontrollerCase2"
|
final val MicrocontrollerCaseTier2 = "microcontrollerCase2"
|
||||||
|
@ -54,6 +54,8 @@ object GuiHandler extends CommonGuiHandler {
|
|||||||
}
|
}
|
||||||
case Some(GuiType.Category.Item) =>
|
case Some(GuiType.Category.Item) =>
|
||||||
Delegator.subItem(player.getCurrentEquippedItem) match {
|
Delegator.subItem(player.getCurrentEquippedItem) match {
|
||||||
|
case Some(manual: item.Manual) if id == GuiType.Manual.id =>
|
||||||
|
new gui.Manual()
|
||||||
case Some(database: item.UpgradeDatabase) if id == GuiType.Database.id =>
|
case Some(database: item.UpgradeDatabase) if id == GuiType.Database.id =>
|
||||||
new gui.Database(player.inventory, new DatabaseInventory {
|
new gui.Database(player.inventory, new DatabaseInventory {
|
||||||
override def tier = database.tier
|
override def tier = database.tier
|
||||||
|
@ -25,6 +25,7 @@ object Textures {
|
|||||||
val guiDisassembler = new ResourceLocation(Settings.resourceDomain, "textures/gui/disassembler.png")
|
val guiDisassembler = new ResourceLocation(Settings.resourceDomain, "textures/gui/disassembler.png")
|
||||||
val guiDrone = new ResourceLocation(Settings.resourceDomain, "textures/gui/drone.png")
|
val guiDrone = new ResourceLocation(Settings.resourceDomain, "textures/gui/drone.png")
|
||||||
val guiKeyboardMissing = new ResourceLocation(Settings.resourceDomain, "textures/gui/keyboard_missing.png")
|
val guiKeyboardMissing = new ResourceLocation(Settings.resourceDomain, "textures/gui/keyboard_missing.png")
|
||||||
|
val guiManual = new ResourceLocation(Settings.resourceDomain, "textures/gui/manual.png")
|
||||||
val guiPrinter = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer.png")
|
val guiPrinter = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer.png")
|
||||||
val guiPrinterInk = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer_ink.png")
|
val guiPrinterInk = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer_ink.png")
|
||||||
val guiPrinterMaterial = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer_material.png")
|
val guiPrinterMaterial = new ResourceLocation(Settings.resourceDomain, "textures/gui/printer_material.png")
|
||||||
|
48
src/main/scala/li/cil/oc/client/gui/Manual.scala
Normal file
48
src/main/scala/li/cil/oc/client/gui/Manual.scala
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package li.cil.oc.client.gui
|
||||||
|
|
||||||
|
import li.cil.oc.client.Textures
|
||||||
|
import li.cil.oc.util.PseudoMarkdown
|
||||||
|
import net.minecraft.client.Minecraft
|
||||||
|
import net.minecraft.client.gui.Gui
|
||||||
|
import net.minecraft.client.gui.GuiScreen
|
||||||
|
import net.minecraft.client.gui.ScaledResolution
|
||||||
|
|
||||||
|
class Manual extends GuiScreen {
|
||||||
|
val document = PseudoMarkdown.parse( """# Headline
|
||||||
|
|
|
||||||
|
|The Adapter block is the core of most of OpenComputers' mod integration.
|
||||||
|
|
|
||||||
|
|*This* is *italic* text, ~~strikethrough~~ maybe a-ter **some** text **in bold**. Is _this underlined_? Oh, no, _it's also italic!_ Well, this \*isn't bold*.
|
||||||
|
|
|
||||||
|
|## Smaller headline
|
||||||
|
|
|
||||||
|
|This is *italic
|
||||||
|
|over two* lines. But *this ... no *this is* **_bold italic_** *text*.
|
||||||
|
|
|
||||||
|
|### even smaller
|
||||||
|
|
|
||||||
|
|*not italic *because ** why would it be*eh
|
||||||
|
|
|
||||||
|
|isn't*.
|
||||||
|
|
|
||||||
|
| # not a header
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|
|
||||||
|
|And finally, [this is a link!](https://avatars1.githubusercontent.com/u/514903).""".stripMargin)
|
||||||
|
|
||||||
|
override def drawScreen(mouseX: Int, mouseY: Int, dt: Float): Unit = {
|
||||||
|
val mc = Minecraft.getMinecraft
|
||||||
|
val screenSize = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight)
|
||||||
|
val guiSize = new ScaledResolution(mc, 256, 192)
|
||||||
|
val (midX, midY) = (screenSize.getScaledWidth / 2, screenSize.getScaledHeight / 2)
|
||||||
|
val (left, top) = (midX - guiSize.getScaledWidth / 2, midY - guiSize.getScaledHeight / 2)
|
||||||
|
|
||||||
|
mc.renderEngine.bindTexture(Textures.guiManual)
|
||||||
|
Gui.func_146110_a(left, top, 0, 0, guiSize.getScaledWidth, guiSize.getScaledHeight, 256, 192)
|
||||||
|
|
||||||
|
super.drawScreen(mouseX, mouseY, dt)
|
||||||
|
|
||||||
|
PseudoMarkdown.render(document, left + 8, top + 8, 220, 176, 0, fontRendererObj)
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ object GuiType extends ScalaEnum {
|
|||||||
val Disassembler = new EnumVal { def name = "Disassembler"; def subType = GuiType.Category.Block }
|
val Disassembler = new EnumVal { def name = "Disassembler"; def subType = GuiType.Category.Block }
|
||||||
val DiskDrive = new EnumVal { def name = "DiskDrive"; def subType = GuiType.Category.Block }
|
val DiskDrive = new EnumVal { def name = "DiskDrive"; def subType = GuiType.Category.Block }
|
||||||
val Drone = new EnumVal { def name = "Drone"; def subType = GuiType.Category.Entity }
|
val Drone = new EnumVal { def name = "Drone"; def subType = GuiType.Category.Entity }
|
||||||
|
val Manual = new EnumVal { def name = "Manual"; def subType = GuiType.Category.Item }
|
||||||
val Printer = new EnumVal { def name = "Printer"; def subType = GuiType.Category.Block }
|
val Printer = new EnumVal { def name = "Printer"; def subType = GuiType.Category.Block }
|
||||||
val Rack = new EnumVal { def name = "Rack"; def subType = GuiType.Category.Block }
|
val Rack = new EnumVal { def name = "Rack"; def subType = GuiType.Category.Block }
|
||||||
val Raid = new EnumVal { def name = "Raid"; def subType = GuiType.Category.Block }
|
val Raid = new EnumVal { def name = "Raid"; def subType = GuiType.Category.Block }
|
||||||
|
@ -458,5 +458,8 @@ object Items extends ItemAPI {
|
|||||||
Recipes.addSubItem(new item.InkCartridge(multi), Constants.ItemName.InkCartridge, "oc:inkCartridge")
|
Recipes.addSubItem(new item.InkCartridge(multi), Constants.ItemName.InkCartridge, "oc:inkCartridge")
|
||||||
Recipes.addSubItem(new item.Chamelium(multi), Constants.ItemName.Chamelium, "oc:chamelium")
|
Recipes.addSubItem(new item.Chamelium(multi), Constants.ItemName.Chamelium, "oc:chamelium")
|
||||||
Recipes.addSubItem(new item.TexturePicker(multi), Constants.ItemName.TexturePicker, "oc:texturePicker")
|
Recipes.addSubItem(new item.TexturePicker(multi), Constants.ItemName.TexturePicker, "oc:texturePicker")
|
||||||
|
|
||||||
|
// 1.5.7
|
||||||
|
Recipes.addSubItem(new item.Manual(multi), Constants.ItemName.Manual, "oc:manual")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
src/main/scala/li/cil/oc/common/item/Manual.scala
Normal file
14
src/main/scala/li/cil/oc/common/item/Manual.scala
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
package li.cil.oc.common.item
|
||||||
|
|
||||||
|
import li.cil.oc.OpenComputers
|
||||||
|
import li.cil.oc.common.GuiType
|
||||||
|
import net.minecraft.entity.player.EntityPlayer
|
||||||
|
import net.minecraft.item.ItemStack
|
||||||
|
import net.minecraft.world.World
|
||||||
|
|
||||||
|
class Manual(val parent: Delegator) extends Delegate {
|
||||||
|
override def onItemRightClick(stack: ItemStack, world: World, player: EntityPlayer): ItemStack = {
|
||||||
|
player.openGui(OpenComputers, GuiType.Manual.id, world, 0, 0, 0)
|
||||||
|
super.onItemRightClick(stack, world, player)
|
||||||
|
}
|
||||||
|
}
|
243
src/main/scala/li/cil/oc/util/PseudoMarkdown.scala
Normal file
243
src/main/scala/li/cil/oc/util/PseudoMarkdown.scala
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
package li.cil.oc.util
|
||||||
|
|
||||||
|
import net.minecraft.client.gui.FontRenderer
|
||||||
|
import net.minecraft.util.EnumChatFormatting
|
||||||
|
import org.lwjgl.opengl.GL11
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.util.matching.Regex
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primitive Markdown parser, only supports a very small subset. Used for
|
||||||
|
* parsing documentation into segments, to be displayed in a GUI somewhere.
|
||||||
|
*/
|
||||||
|
object PseudoMarkdown {
|
||||||
|
/**
|
||||||
|
* Parses a plain text document into a list of segments.
|
||||||
|
*/
|
||||||
|
def parse(document: String): Iterable[Segment] = {
|
||||||
|
var segments = document.lines.flatMap(line => Iterable(new TextSegment(null, line), new NewLineSegment())).toArray
|
||||||
|
for ((pattern, factory) <- segmentTypes) {
|
||||||
|
segments = segments.flatMap(_.refine(pattern, factory))
|
||||||
|
}
|
||||||
|
segments
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a list of segments and tooltips if a segment with a tooltip is hovered.
|
||||||
|
* Returns a link address if a link is hovered.
|
||||||
|
*/
|
||||||
|
def render(document: Iterable[Segment], x: Int, y: Int, maxWidth: Int, height: Int, offset: Int, renderer: FontRenderer): Option[String] = {
|
||||||
|
var currentX = 0
|
||||||
|
var currentY = 0
|
||||||
|
for (segment <- document) {
|
||||||
|
if (currentY >= offset) {
|
||||||
|
segment.render(x, y + currentY, currentX, maxWidth, renderer)
|
||||||
|
}
|
||||||
|
currentY += segment.height(currentX, maxWidth, renderer) - renderer.FONT_HEIGHT
|
||||||
|
currentX = segment.width(currentX, maxWidth, renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
trait Segment {
|
||||||
|
// Used when rendering, to compute the style of a nested segment.
|
||||||
|
protected def parent: Segment
|
||||||
|
|
||||||
|
// Used during construction, checks a segment for inner segments.
|
||||||
|
private[PseudoMarkdown] def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = Iterable(this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the height of this segment, in pixels, given it starts at the
|
||||||
|
* specified indent into the current line, with the specified maximum
|
||||||
|
* allowed width.
|
||||||
|
*/
|
||||||
|
def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = 0
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the width of the last line of this segment, given it starts
|
||||||
|
* at the specified indent into the current line, with the specified
|
||||||
|
* maximum allowed width.
|
||||||
|
* If the segment remains on the same line, returns the new end of the
|
||||||
|
* line (i.e. indent plus width of the segment).
|
||||||
|
*/
|
||||||
|
def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = 0
|
||||||
|
|
||||||
|
def render(x: Int, y: Int, indent: Int, width: Int, renderer: FontRenderer): Unit = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
private class TextSegment(protected val parent: Segment, val text: String) extends Segment {
|
||||||
|
override def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = {
|
||||||
|
val result = mutable.Buffer.empty[Segment]
|
||||||
|
|
||||||
|
// Keep track of last matches end, to generate plain text segments.
|
||||||
|
var textStart = 0
|
||||||
|
for (m <- pattern.findAllMatchIn(text)) {
|
||||||
|
// Create segment for leading plain text.
|
||||||
|
if (m.start > textStart) {
|
||||||
|
result += new TextSegment(this, text.substring(textStart, m.start))
|
||||||
|
}
|
||||||
|
textStart = m.end
|
||||||
|
|
||||||
|
// Create segment for formatted text.
|
||||||
|
result += factory(this, m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create segment for remaining plain text.
|
||||||
|
if (textStart == 0) {
|
||||||
|
result += this
|
||||||
|
}
|
||||||
|
else if (textStart < text.length) {
|
||||||
|
result += new TextSegment(this, text.substring(textStart))
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
|
||||||
|
var lines = 1
|
||||||
|
var chars = text
|
||||||
|
var lineChars = maxChars(chars, maxWidth - indent, renderer)
|
||||||
|
while (chars.length > lineChars) {
|
||||||
|
lines += 1
|
||||||
|
chars = chars.drop(lineChars)
|
||||||
|
lineChars = maxChars(chars, maxWidth, renderer)
|
||||||
|
}
|
||||||
|
lines * renderer.FONT_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
override def width(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
|
||||||
|
var currentX = indent
|
||||||
|
var chars = text
|
||||||
|
var lineChars = maxChars(chars, maxWidth - indent, renderer)
|
||||||
|
while (chars.length > lineChars) {
|
||||||
|
chars = chars.drop(lineChars)
|
||||||
|
lineChars = maxChars(chars, maxWidth, renderer)
|
||||||
|
currentX = 0
|
||||||
|
}
|
||||||
|
currentX + renderer.getStringWidth(fullFormat + chars)
|
||||||
|
}
|
||||||
|
|
||||||
|
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer): Unit = {
|
||||||
|
var currentX = x + indent
|
||||||
|
var currentY = y
|
||||||
|
var chars = text
|
||||||
|
var numChars = maxChars(chars, maxWidth - indent, renderer)
|
||||||
|
while (chars.length > 0) {
|
||||||
|
renderer.drawString(fullFormat + chars.take(numChars), currentX, currentY, 0xFFFFFF)
|
||||||
|
currentX = x
|
||||||
|
currentY += renderer.FONT_HEIGHT
|
||||||
|
chars = chars.drop(numChars)
|
||||||
|
numChars = maxChars(chars, maxWidth, renderer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def format = ""
|
||||||
|
|
||||||
|
protected def stringWidth(s: String, renderer: FontRenderer): Int = renderer.getStringWidth(s)
|
||||||
|
|
||||||
|
private def fullFormat = parent match {
|
||||||
|
case segment: TextSegment => segment.format + format
|
||||||
|
case _ => format
|
||||||
|
}
|
||||||
|
|
||||||
|
private def maxChars(s: String, maxWidth: Int, renderer: FontRenderer): Int = {
|
||||||
|
val breaks = Set(' ', '-', '.', '+', '*', '_', '/')
|
||||||
|
var pos = 1
|
||||||
|
var lastBreak = -1
|
||||||
|
while (pos < s.length) {
|
||||||
|
val width = stringWidth(fullFormat + s.take(pos), renderer)
|
||||||
|
if (breaks.contains(s.charAt(pos))) lastBreak = pos
|
||||||
|
if (width > maxWidth) return lastBreak + 1
|
||||||
|
pos += 1
|
||||||
|
}
|
||||||
|
pos
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"{TextSegment: text = $text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderSegment(parent: Segment, text: String, val level: Int) extends TextSegment(parent, text) {
|
||||||
|
private def scale = math.max(2, 5 - level) / 2f
|
||||||
|
|
||||||
|
override protected def format = EnumChatFormatting.BOLD.toString
|
||||||
|
|
||||||
|
override protected def stringWidth(s: String, renderer: FontRenderer): Int = (super.stringWidth(s, renderer) * scale).toInt
|
||||||
|
|
||||||
|
override def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = (super.height(indent, maxWidth, renderer) * scale).toInt
|
||||||
|
|
||||||
|
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer): Unit = {
|
||||||
|
GL11.glPushMatrix()
|
||||||
|
GL11.glTranslatef(x, y, 0)
|
||||||
|
GL11.glScalef(scale, scale, scale)
|
||||||
|
GL11.glTranslatef(-x, -y, 0)
|
||||||
|
super.render(x, y, indent, maxWidth, renderer)
|
||||||
|
GL11.glPopMatrix()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def toString: String = s"{HeaderSegment: text = $text, level = $level}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LinkSegment(parent: Segment, text: String, val url: String) extends TextSegment(parent, text) {
|
||||||
|
override def toString: String = s"{LinkSegment: text = $text, url = $url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BoldSegment(parent: Segment, text: String) extends TextSegment(parent, text) {
|
||||||
|
override protected def format = EnumChatFormatting.BOLD.toString
|
||||||
|
|
||||||
|
override def toString: String = s"{BoldSegment: text = $text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ItalicSegment(parent: Segment, text: String) extends TextSegment(parent, text) {
|
||||||
|
override protected def format = EnumChatFormatting.ITALIC.toString
|
||||||
|
|
||||||
|
override def toString: String = s"{ItalicSegment: text = $text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StrikethroughSegment(parent: Segment, text: String) extends TextSegment(parent, text) {
|
||||||
|
override protected def format = EnumChatFormatting.STRIKETHROUGH.toString
|
||||||
|
|
||||||
|
override def toString: String = s"{StrikethroughSegment: text = $text}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImageSegment(val parent: Segment, val tooltip: String, val url: String) extends Segment {
|
||||||
|
override def toString: String = s"{ImageSegment: tooltip = $tooltip, url = $url}"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class NewLineSegment extends Segment {
|
||||||
|
override protected def parent: Segment = null
|
||||||
|
|
||||||
|
override def height(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = renderer.FONT_HEIGHT * 2
|
||||||
|
|
||||||
|
override def toString: String = s"{NewLineSegment}"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------- //
|
||||||
|
|
||||||
|
private def HeaderSegment(s: Segment, m: Regex.Match) = new HeaderSegment(s, m.group(2), m.group(1).length)
|
||||||
|
|
||||||
|
private def LinkSegment(s: Segment, m: Regex.Match) = new LinkSegment(s, m.group(1), m.group(2))
|
||||||
|
|
||||||
|
private def BoldSegment(s: Segment, m: Regex.Match) = new BoldSegment(s, m.group(2))
|
||||||
|
|
||||||
|
private def ItalicSegment(s: Segment, m: Regex.Match) = new ItalicSegment(s, m.group(2))
|
||||||
|
|
||||||
|
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 val segmentTypes = Array(
|
||||||
|
"""^(#+)\s(.*)""".r -> HeaderSegment _, // headers: # ...
|
||||||
|
"""!\[([^\[]*)\]\(([^\)]+)\)""".r -> ImageSegment _, // images: 
|
||||||
|
"""\[([^\[]+)\]\(([^\)]+)\)""".r -> LinkSegment _, // links: [...](...)
|
||||||
|
"""(\*\*|__)(\S.*?\S|$)\1""".r -> BoldSegment _, // bold: **...** | __...__
|
||||||
|
"""(\*|_)(\S.*?\S|$)\1""".r -> ItalicSegment _, // italic: *...* | _..._
|
||||||
|
"""~~(\S.*?\S|$)~~""".r -> StrikethroughSegment _ // strikethrough: ~~...~~
|
||||||
|
)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user