From fe66cec9d6d719c32b83045bce66af23f17df03f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20N=C3=BCcke?= Date: Sun, 12 Apr 2015 14:49:33 +0200 Subject: [PATCH] More cleanup, pulled common functionality from text and code segments into trait. --- .../markdown/segment/BasicTextSegment.scala | 68 ++++++++++ .../markdown/segment/CodeSegment.scala | 60 +------- .../renderer/markdown/segment/Segment.scala | 6 + .../markdown/segment/TextSegment.scala | 128 ++++++------------ 4 files changed, 120 insertions(+), 142 deletions(-) create mode 100644 src/main/scala/li/cil/oc/client/renderer/markdown/segment/BasicTextSegment.scala diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/BasicTextSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/BasicTextSegment.scala new file mode 100644 index 000000000..7fd3e328f --- /dev/null +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/BasicTextSegment.scala @@ -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 +} diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala index 1149769ae..46f36f4aa 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/CodeSegment.scala @@ -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}" } diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/Segment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/Segment.scala index dd35430f1..7e6ca0527 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/Segment.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/Segment.scala @@ -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) diff --git a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala index dd2b2c917..e146dccd1 100644 --- a/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala +++ b/src/main/scala/li/cil/oc/client/renderer/markdown/segment/TextSegment.scala @@ -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}" }