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 package li.cil.oc.client.renderer.markdown.segment
import li.cil.oc.client.renderer.TextBufferRenderCache import li.cil.oc.client.renderer.TextBufferRenderCache
import li.cil.oc.client.renderer.markdown.Document
import net.minecraft.client.gui.FontRenderer import net.minecraft.client.gui.FontRenderer
import org.lwjgl.opengl.GL11 import org.lwjgl.opengl.GL11
private[markdown] class CodeSegment(val parent: Segment, val text: String) extends Segment { private[markdown] class CodeSegment(val parent: Segment, val text: String) extends BasicTextSegment {
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)
}
override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
TextBufferRenderCache.renderer.generateChars(text.toCharArray) TextBufferRenderCache.renderer.generateChars(text.toCharArray)
@ -45,38 +12,23 @@ private[markdown] class CodeSegment(val parent: Segment, val text: String) exten
var currentY = y var currentY = y
var chars = text var chars = text
val wrapIndent = computeWrapIndent(renderer) 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) { while (chars.length > 0) {
val part = chars.take(numChars) val part = chars.take(numChars)
GL11.glColor4f(0.75f, 0.8f, 1, 1) GL11.glColor4f(0.75f, 0.8f, 1, 1)
TextBufferRenderCache.renderer.drawString(part, currentX, currentY) TextBufferRenderCache.renderer.drawString(part, currentX, currentY)
currentX = x + wrapIndent currentX = x + wrapIndent
currentY += Document.lineHeight(renderer) currentY += lineHeight(renderer)
chars = chars.drop(numChars).dropWhile(_.isWhitespace) chars = chars.drop(numChars).dropWhile(_.isWhitespace)
numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent) numChars = maxChars(chars, maxWidth - wrapIndent, maxWidth - wrapIndent, renderer)
} }
None 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 = { override protected def stringWidth(s: String, renderer: FontRenderer): Int = s.length * TextBufferRenderCache.renderer.charRenderWidth
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 def toString: String = s"{CodeSegment: text = $text}" 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 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 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. // Used during construction, checks a segment for inner segments.
private[markdown] def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = Iterable(this) 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.collection.mutable
import scala.util.matching.Regex import scala.util.matching.Regex
private[markdown] class TextSegment(val parent: Segment, val text: String) extends Segment { private[markdown] class TextSegment(val parent: Segment, val text: String) extends BasicTextSegment {
private final val breaks = Set(' ', '.', ',', ':', ';', '!', '?', '_', '=', '-', '+', '*', '/', '\\') override def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = {
private final val lists = Set("- ", "* ") var currentX = x + indent
private lazy val rootPrefix = root.asInstanceOf[TextSegment].text.take(2) 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] = { override def refine(pattern: Regex, factory: (Segment, Regex.Match) => Segment): Iterable[Segment] = {
val result = mutable.Buffer.empty[Segment] val result = mutable.Buffer.empty[Segment]
@ -39,63 +61,13 @@ private[markdown] class TextSegment(val parent: Segment, val text: String) exten
result 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 = { override protected def lineHeight(renderer: FontRenderer): Int = (super.lineHeight(renderer) * resolvedScale).toInt
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 def render(x: Int, y: Int, indent: Int, maxWidth: Int, renderer: FontRenderer, mouseX: Int, mouseY: Int): Option[InteractiveSegment] = { override protected def stringWidth(s: String, renderer: FontRenderer): Int = (renderer.getStringWidth(resolvedFormat + s) * resolvedScale).toInt
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)
}
hovered // ----------------------------------------------------------------------- //
}
protected def color = None: Option[Int] 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 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 { private def resolvedScale: Float = parent match {
case segment: TextSegment => color.getOrElse(segment.resolvedColor) case segment: TextSegment => scale.getOrElse(1f) * segment.resolvedScale
case _ => color.getOrElse(0xDDDDDD) case _ => 1f
} }
def resolvedScale: Float = parent match { private def resolvedFormat: String = parent match {
case segment: TextSegment => scale.getOrElse(segment.resolvedScale)
case _ => scale.getOrElse(1f)
}
def resolvedFormat: String = parent match {
case segment: TextSegment => segment.resolvedFormat + format case segment: TextSegment => segment.resolvedFormat + format
case _ => 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 segment: InteractiveSegment => Some(segment)
case _ => parent match { case _ => parent match {
case segment: TextSegment => segment.findInteractive() case segment: TextSegment => segment.resolvedInteractive
case _ => None 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}" override def toString: String = s"{TextSegment: text = $text}"
} }