More cleanup, pulled common functionality from text and code segments into trait.

This commit is contained in:
Florian Nücke 2015-04-12 14:49:33 +02:00
parent c6bfcb464e
commit fe66cec9d6
4 changed files with 120 additions and 142 deletions

View File

@ -0,0 +1,68 @@
package li.cil.oc.client.renderer.markdown.segment
import li.cil.oc.client.renderer.markdown.Document
import net.minecraft.client.gui.FontRenderer
trait BasicTextSegment extends Segment {
protected final val breaks = Set(' ', '.', ',', ':', ';', '!', '?', '_', '=', '-', '+', '*', '/', '\\')
protected final val lists = Set("- ", "* ")
protected lazy val rootPrefix = root.asInstanceOf[TextSegment].text.take(2)
override def nextX(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
if (isLast) return 0
var currentX = indent
var chars = text
if (ignoreLeadingWhitespace && indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
while (chars.length > numChars) {
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
currentX = wrapIndent
}
currentX + stringWidth(chars, renderer)
}
override def nextY(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
var lines = 0
var chars = text
if (ignoreLeadingWhitespace && indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
while (chars.length > numChars) {
lines += 1
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
}
if (isLast) lines += 1
lines * lineHeight(renderer)
}
// ----------------------------------------------------------------------- //
protected def text: String
protected def ignoreLeadingWhitespace: Boolean = true
protected def lineHeight(renderer: FontRenderer): Int = Document.lineHeight(renderer)
protected def stringWidth(s: String, renderer: FontRenderer): Int
protected def maxChars(s: String, maxWidth: Int, maxLineWidth: Int, renderer: FontRenderer): Int = {
var pos = -1
var lastBreak = -1
val fullWidth = stringWidth(s, renderer)
while (pos < s.length) {
pos += 1
val width = stringWidth(s.take(pos), renderer)
if (width >= maxWidth) {
if (lastBreak > 0 || fullWidth <= maxLineWidth || s.exists(breaks.contains)) return lastBreak + 1
else return pos - 1
}
if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos
}
pos
}
protected def computeWrapIndent(renderer: FontRenderer) = if (lists.contains(rootPrefix)) renderer.getStringWidth(rootPrefix) else 0
}

View File

@ -1,43 +1,10 @@
package li.cil.oc.client.renderer.markdown.segment
import li.cil.oc.client.renderer.TextBufferRenderCache
import li.cil.oc.client.renderer.markdown.Document
import net.minecraft.client.gui.FontRenderer
import org.lwjgl.opengl.GL11
private[markdown] class CodeSegment(val parent: Segment, val text: String) extends Segment {
private final val breaks = Set(' ', '.', ',', ':', ';', '!', '?', '_', '=', '-', '+', '*', '/', '\\')
private final val lists = Set("- ", "* ")
private lazy val rootPrefix = root.asInstanceOf[TextSegment].text.take(2)
override def nextX(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
if (isLast) return 0
var currentX = indent
var chars = text
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent)
while (chars.length > numChars) {
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent)
currentX = wrapIndent + 1
}
currentX + stringWidth(chars)
}
override def nextY(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
var lines = 0
var chars = text
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent)
while (chars.length > numChars) {
lines += 1
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent)
}
if (isLast) lines += 1
lines * Document.lineHeight(renderer)
}
private[markdown] class CodeSegment(val parent: Segment, val text: String) extends BasicTextSegment {
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
TextBufferRenderCache.renderer.generateChars(text.toCharArray)
@ -45,38 +12,23 @@ private[markdown] class CodeSegment(val parent: Segment, val text: String) exten
var currentY = y
var chars = text
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
while (chars.length > 0) {
val part = chars.take(numChars)
GL11.glColor4f(0.75f, 0.8f, 1, 1)
TextBufferRenderCache.renderer.drawString(part, currentX, currentY)
currentX = x + wrapIndent
currentY += Document.lineHeight(renderer)
currentY += lineHeight(renderer)
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
}
None
}
private def stringWidth(s: String): Int = s.length * TextBufferRenderCache.renderer.charRenderWidth
override protected def ignoreLeadingWhitespace: Boolean = false
private def maxChars(s: String, maxWidth: Int, maxLineWidth: Int): Int = {
var pos = 0
var lastBreak = -1
while (pos < s.length) {
pos += 1
val width = stringWidth(s.take(pos))
if (width >= maxWidth) {
if (lastBreak > 0 || stringWidth(s) <= maxLineWidth || s.exists(breaks.contains)) return lastBreak + 1
else return pos - 1
}
if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos
}
pos
}
private def computeWrapIndent(renderer: FontRenderer) = if (lists.contains(rootPrefix)) renderer.getStringWidth(rootPrefix) else 0
override protected def stringWidth(s: String, renderer: FontRenderer): Int = s.length * TextBufferRenderCache.renderer.charRenderWidth
override def toString: String = s"{CodeSegment: text = $text}"
}

View File

@ -40,8 +40,14 @@ trait Segment {
*/
def nextY(indent: Int, maxWidth: Int, renderer: FontRenderer): Int
/**
* Render the segment at the specified coordinates with the specified
* properties.
*/
def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = None
// ----------------------------------------------------------------------- //
// Used during construction, checks a segment for inner segments.
private[markdown] def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = Iterable(this)

View File

@ -8,10 +8,32 @@ import scala.annotation.tailrec
import scala.collection.mutable
import scala.util.matching.Regex
private[markdown] class TextSegment(val parent: Segment, val text: String) extends Segment {
private final val breaks = Set(' ', '.', ',', ':', ';', '!', '?', '_', '=', '-', '+', '*', '/', '\\')
private final val lists = Set("- ", "* ")
private lazy val rootPrefix = root.asInstanceOf[TextSegment].text.take(2)
private[markdown] class TextSegment(val parent: Segment, val text: String) extends BasicTextSegment {
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
var currentX = x + indent
var currentY = y
var chars = text
if (indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
var hovered: Option[InteractiveSegment] = None
while (chars.length > 0) {
val part = chars.take(numChars)
hovered = hovered.orElse(resolvedInteractive.fold(None: Option[InteractiveSegment])(_.checkHovered(mouseX, mouseY, currentX, currentY, stringWidth(part, renderer), (Document.lineHeight(renderer) * resolvedScale).toInt)))
GL11.glPushMatrix()
GL11.glTranslatef(currentX, currentY, 0)
GL11.glScalef(resolvedScale, resolvedScale, resolvedScale)
GL11.glTranslatef(-currentX, -currentY, 0)
renderer.drawString(resolvedFormat + part, currentX, currentY, resolvedColor)
GL11.glPopMatrix()
currentX = x + wrapIndent
currentY += lineHeight(renderer)
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
}
hovered
}
override def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = {
val result = mutable.Buffer.empty[Segment]
@ -39,63 +61,13 @@ private[markdown] class TextSegment(val parent: Segment, val text: String) exten
result
}
override def nextX(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
if (isLast) return 0
var currentX = indent
var chars = text
if (indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
while (chars.length > numChars) {
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
currentX = wrapIndent
}
currentX + (stringWidth(chars, renderer) * resolvedScale).toInt
}
// ----------------------------------------------------------------------- //
override def nextY(indent: Int, maxWidth: Int, renderer: FontRenderer): Int = {
var lines = 0
var chars = text
if (indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
while (chars.length > numChars) {
lines += 1
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
}
if (isLast) lines += 1
(lines * Document.lineHeight(renderer) * resolvedScale).toInt
}
override protected def lineHeight(renderer: FontRenderer): Int = (super.lineHeight(renderer) * resolvedScale).toInt
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
val fontScale = resolvedScale
var currentX = x + indent
var currentY = y
var chars = text
if (indent == 0) chars = chars.dropWhile(_.isWhitespace)
val wrapIndent = computeWrapIndent(renderer)
var numChars = maxChars(chars, maxWidth - indent, maxWidth - wrapIndent, renderer)
val interactive = findInteractive()
var hovered: Option[InteractiveSegment] = None
while (chars.length > 0) {
val part = chars.take(numChars)
hovered = hovered.orElse(interactive.fold(None: Option[InteractiveSegment])(_.checkHovered(mouseX, mouseY, currentX, currentY, (stringWidth(part, renderer) * fontScale).toInt, (Document.lineHeight(renderer) * fontScale).toInt)))
GL11.glPushMatrix()
GL11.glTranslatef(currentX, currentY, 0)
GL11.glScalef(fontScale, fontScale, fontScale)
GL11.glTranslatef(-currentX, -currentY, 0)
renderer.drawString(resolvedFormat + part, currentX, currentY, resolvedColor)
GL11.glPopMatrix()
currentX = x + wrapIndent
currentY += (Document.lineHeight(renderer) * fontScale).toInt
chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
}
override protected def stringWidth(s: String, renderer: FontRenderer): Int = (renderer.getStringWidth(resolvedFormat + s) * resolvedScale).toInt
hovered
}
// ----------------------------------------------------------------------- //
protected def color = None: Option[Int]
@ -103,48 +75,28 @@ private[markdown] class TextSegment(val parent: Segment, val text: String) exten
protected def format = ""
protected def stringWidth(s: String, renderer: FontRenderer): Int = renderer.getStringWidth(resolvedFormat + s)
private def resolvedColor: Int = color.getOrElse(parent match {
case segment: TextSegment => segment.resolvedColor
case _ => 0xDDDDDD
})
def resolvedColor: Int = parent match {
case segment: TextSegment => color.getOrElse(segment.resolvedColor)
case _ => color.getOrElse(0xDDDDDD)
private def resolvedScale: Float = parent match {
case segment: TextSegment => scale.getOrElse(1f) * segment.resolvedScale
case _ => 1f
}
def resolvedScale: Float = parent match {
case segment: TextSegment => scale.getOrElse(segment.resolvedScale)
case _ => scale.getOrElse(1f)
}
def resolvedFormat: String = parent match {
private def resolvedFormat: String = parent match {
case segment: TextSegment => segment.resolvedFormat + format
case _ => format
}
@tailrec private def findInteractive(): Option[InteractiveSegment] = this match {
private lazy val resolvedInteractive: Option[InteractiveSegment] = this match {
case segment: InteractiveSegment => Some(segment)
case _ => parent match {
case segment: TextSegment => segment.findInteractive()
case segment: TextSegment => segment.resolvedInteractive
case _ => None
}
}
private def maxChars(s: String, maxWidth: Int, maxLineWidth: Int, renderer: FontRenderer): Int = {
val fontScale = resolvedScale
var pos = -1
var lastBreak = -1
while (pos < s.length) {
pos += 1
val width = (stringWidth(s.take(pos), renderer) * fontScale).toInt
if (width >= maxWidth) {
if (lastBreak > 0 || stringWidth(s, renderer) <= maxLineWidth || s.exists(breaks.contains)) return lastBreak + 1
else return pos - 1
}
if (pos < s.length && breaks.contains(s.charAt(pos))) lastBreak = pos
}
pos
}
private def computeWrapIndent(renderer: FontRenderer) = if (lists.contains(rootPrefix)) renderer.getStringWidth(rootPrefix) else 0
override def toString: String = s"{TextSegment: text = $text}"
}