package uk.co.nickthecoder.gamescupboard.client
import com.soywiz.korge.view.ClipContainer
import com.soywiz.korge.view.solidRect
import com.soywiz.korim.color.Colors
import com.soywiz.korim.color.RGBA
import uk.co.nickthecoder.gamescupboard.common.dockHeight
private open class RulesStyle(
val prefix: String,
val suffix: String,
val color: RGBA
)
private class HyperlinkStyle(val separator: String = "|") : RulesStyle("[", "]", hyperlinks)
class Rules(str: String) : TextButtonDockable("Rules") {
override val panel = ClipContainer(400.0, dockHeight.toDouble()).apply {
solidRect(width, height, dockableColor)
}
init {
// Within the text, we can change styles.
// This map is keyed on the string which starts and ends the style.
// The values are RGBA colors for that style.
// e.g. For fake-bold text, place words in backticks `Hello World`.
// NOTE. Styles cannot intersect. So `Hello _world_` is not allowed, because the fake-underscore
// is within the fake-bold. In this example, the output would be "Hello _world_" in fake-bold's color.
val styles = listOf(
RulesStyle("`", "`", boldText),
RulesStyle("_", "_", underscoreText),
HyperlinkStyle()
)
val printContainer =
PrintContainer(panel.width, panel.height - 20, paddingTop = 10.0, paddingLeft = 20.0).apply {
backgroundColor = Colors.TRANSPARENT_WHITE
}
panel.addChild(printContainer)
val lines = str.split("\n")
// We start off with no styles. When we loop through each line, this holds the style in force at the
// end of that line (and is used as the initial style for the next line).
var trailingStyle: RulesStyle? = null
// Start of nested functions
/**
* Prints to printContainer, using the style according to [style].
* If it is null, then the "plain" style is used.
* Otherwise, look up the color in the styles map.
*/
fun styledPrint(str: String, style: RulesStyle?, action: (() -> Unit)? = null) {
if (style == null) {
printContainer.print(str)
} else {
printContainer.print(str, style.color, action)
}
}
/**
* Prints str to the printContainer. The initial style is `initialStyle`.
* However, if we find a new style within [str], then we use recursion to
* print the parts.
* Note. the first level of recursion, [initialStyle] = `trailingStyleKey`
* i.e. the style left over from the previous line.
* For nested calls, [initialStyle] is the style left over from the previous
* level of recursion.
*
* When we recurse, [str] will always be smaller, so we are guaranteed to exit.
*
* The return value is the RulesStyle that has not been closed, or null
* when [str] ends with no styles applied.
*/
fun processRemainder(str: String, initialStyle: RulesStyle?): RulesStyle? {
if (str.isBlank()) return initialStyle
if (initialStyle == null) {
// Look for the FIRST beginning of one of the special styles ` or _
var lowestIndex = Int.MAX_VALUE
var foundStyle: RulesStyle? = null
for (style in styles) {
val i = str.indexOf(style.prefix)
if (i >= 0 && i < lowestIndex) {
lowestIndex = i
foundStyle = style
}
}
if (foundStyle != null && lowestIndex != Int.MAX_VALUE) {
// We've found a special style, so print up to that character
styledPrint(str.substring(0, lowestIndex), null)
val endIndex = str.indexOf(foundStyle.suffix, lowestIndex + foundStyle.prefix.length)
if (endIndex >= 0) {
// The end of the style is on the same line
if (foundStyle is HyperlinkStyle) {
val sepIndex = str.indexOf(foundStyle.separator, lowestIndex + foundStyle.prefix.length)
if (sepIndex < 0) {
// No separator, so the url and link-text are one and the same.
val linkText = str.substring(lowestIndex + foundStyle.prefix.length, endIndex)
styledPrint(linkText, foundStyle) { FollowLink.follow(linkText) }
} else {
// We found the separator, so print up to that (which is the text).
// The url is after sepIndex
val linkText = str.substring(lowestIndex + foundStyle.prefix.length, sepIndex)
val url = str.substring(sepIndex + foundStyle.separator.length, endIndex)
styledPrint(linkText, foundStyle) { FollowLink.follow(url) }
}
} else {
styledPrint(str.substring(lowestIndex + foundStyle.prefix.length, endIndex), foundStyle)
}
// Now recursively process the remainder of the line.
return processRemainder(str.substring(endIndex + foundStyle.suffix.length), null)
} else {
// There is no end-of-style on this line.
styledPrint(str.substring(lowestIndex + foundStyle.prefix.length), foundStyle)
return null
}
} else {
// There are no styles, so print it plain.
styledPrint(str, null)
return null
}
} else {
// The line started with a special style, so we need to look for the close
val closeIndex = str.indexOf(initialStyle.suffix)
if (closeIndex >= 0) {
// We've found the end.
styledPrint(str.substring(0, closeIndex), initialStyle)
return processRemainder(str.substring(closeIndex + initialStyle.suffix.length), null)
} else {
// There is no close, so use the same style for the whole line
styledPrint(str, initialStyle)
return initialStyle
}
}
}
// End of nested functions!
for (line in lines) {
if (line.isBlank()) {
printContainer.println()
printContainer.println()
} else {
// Process a single line of text.
trailingStyle = processRemainder(line, trailingStyle)
// At the end of each line, we add an extra space, so that newlines are treated as whitespace.
printContainer.print(" ")
}
}
printContainer.scrollTopRatio = 0.0
}
}