|  | package li.cil.oc.util | 
|  |  | 
|  | import li.cil.oc.Settings | 
|  | import li.cil.oc.api.component.TextBuffer.ColorDepth | 
|  | import net.minecraft.nbt._ | 
|  |  | 
|  | /** | 
|  | * This stores chars in a 2D-Array and provides some manipulation functions. | 
|  | * | 
|  | * The main purpose of this is to allow moving most implementation detail to | 
|  | * the Lua side while keeping bandwidth costs low and still allowing for | 
|  | * relatively fast updates, given a smart algorithm (using copy()/fill() | 
|  | * instead of set()ing everything). | 
|  | */ | 
|  | class TextBuffer(var width: Int, var height: Int, initialFormat: PackedColor.ColorFormat) { | 
|  | def this(size: (Int, Int), format: PackedColor.ColorFormat) = this(size._1, size._2, format) | 
|  |  | 
|  | private var _format = initialFormat | 
|  |  | 
|  | private var _foreground = PackedColor.Color(0xFFFFFF) | 
|  |  | 
|  | private var _background = PackedColor.Color(0x000000) | 
|  |  | 
|  | private var packed = PackedColor.pack(_foreground, _background, _format) | 
|  |  | 
|  | def foreground = _foreground | 
|  |  | 
|  | def foreground_=(value: PackedColor.Color) = { | 
|  | format.validate(value) | 
|  | _foreground = value | 
|  | packed = PackedColor.pack(_foreground, _background, _format) | 
|  | this | 
|  | } | 
|  |  | 
|  | def background = _background | 
|  |  | 
|  | def background_=(value: PackedColor.Color) = { | 
|  | format.validate(value) | 
|  | _background = value | 
|  | packed = PackedColor.pack(_foreground, _background, _format) | 
|  | this | 
|  | } | 
|  |  | 
|  | def format = _format | 
|  |  | 
|  | def format_=(value: PackedColor.ColorFormat) = { | 
|  | if (format.depth != value.depth) { | 
|  | for (row <- 0 until height) { | 
|  | val rowColor = color(row) | 
|  | for (col <- 0 until width) { | 
|  | val packed = rowColor(col) | 
|  | val fg = PackedColor.Color(PackedColor.unpackForeground(packed, _format)) | 
|  | val bg = PackedColor.Color(PackedColor.unpackBackground(packed, _format)) | 
|  | rowColor(col) = PackedColor.pack(fg, bg, value) | 
|  | } | 
|  | } | 
|  | _format = value | 
|  | packed = PackedColor.pack(_foreground, _background, _format) | 
|  | true | 
|  | } | 
|  | else false | 
|  | } | 
|  |  | 
|  | var color = Array.fill(height, width)(packed) | 
|  |  | 
|  | var buffer = Array.fill(height, width)(' ') | 
|  |  | 
|  | /** The current buffer size in columns by rows. */ | 
|  | def size = (width, height) | 
|  |  | 
|  | /** | 
|  | * Set the new buffer size, returns true if the size changed. | 
|  | * | 
|  | * This will perform a proper resize as required, keeping as much of the | 
|  | * buffer valid as possible if the size decreases, i.e. only data outside the | 
|  | * new buffer size will be truncated, all data still inside will be copied. | 
|  | */ | 
|  | def size_=(value: (Int, Int)): Boolean = { | 
|  | val (iw, ih) = value | 
|  | val (w, h) = (math.max(iw, 1), math.max(ih, 1)) | 
|  | if (width != w || height != h) { | 
|  | val newBuffer = Array.fill(h, w)(' ') | 
|  | val newColor = Array.fill(h, w)(packed) | 
|  | (0 until math.min(h, height)).foreach(y => { | 
|  | Array.copy(buffer(y), 0, newBuffer(y), 0, math.min(w, width)) | 
|  | Array.copy(color(y), 0, newColor(y), 0, math.min(w, width)) | 
|  | }) | 
|  | buffer = newBuffer | 
|  | color = newColor | 
|  | width = w | 
|  | height = h | 
|  | true | 
|  | } | 
|  | else false | 
|  | } | 
|  |  | 
|  | /** Get the char at the specified index. */ | 
|  | def get(col: Int, row: Int) = buffer(row)(col) | 
|  |  | 
|  | /** String based fill starting at a specified location. */ | 
|  | def set(col: Int, row: Int, s: String, vertical: Boolean): Boolean = | 
|  | if (vertical) { | 
|  | if (col < 0 || col >= width) false | 
|  | else { | 
|  | var changed = false | 
|  | for (y <- row until math.min(row + s.length, height)) if (y >= 0) { | 
|  | val line = buffer(y) | 
|  | val lineColor = color(y) | 
|  | val c = s(y - row) | 
|  | changed = changed || (line(col) != c) || (lineColor(col) != packed) | 
|  | line(col) = c | 
|  | lineColor(col) = packed | 
|  | } | 
|  | changed | 
|  | } | 
|  | } | 
|  | else { | 
|  | if (row < 0 || row >= height) false | 
|  | else { | 
|  | var changed = false | 
|  | val line = buffer(row) | 
|  | val lineColor = color(row) | 
|  | for (x <- col until math.min(col + s.length, width)) if (x >= 0) { | 
|  | val c = s(x - col) | 
|  | changed = changed || (line(x) != c) || (lineColor(x) != packed) | 
|  | line(x) = c | 
|  | lineColor(x) = packed | 
|  | } | 
|  | changed | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Fills an area of the buffer with the specified character. */ | 
|  | def fill(col: Int, row: Int, w: Int, h: Int, c: Char): Boolean = { | 
|  | // Anything to do at all? | 
|  | if (w <= 0 || h <= 0) return false | 
|  | if (col + w < 0 || row + h < 0 || col >= width || row >= height) return false | 
|  | var changed = false | 
|  | for (y <- math.max(row, 0) until math.min(row + h, height)) { | 
|  | val line = buffer(y) | 
|  | val lineColor = color(y) | 
|  | for (x <- math.max(col, 0) until math.min(col + w, width)) { | 
|  | changed = changed || (line(x) != c) || (lineColor(x) != packed) | 
|  | line(x) = c | 
|  | lineColor(x) = packed | 
|  | } | 
|  | } | 
|  | changed | 
|  | } | 
|  |  | 
|  | /** Copies a portion of the buffer. */ | 
|  | def copy(col: Int, row: Int, w: Int, h: Int, tx: Int, ty: Int): Boolean = { | 
|  | // Anything to do at all? | 
|  | if (w <= 0 || h <= 0) return false | 
|  | if (tx == 0 && ty == 0) return false | 
|  | // Loop over the target rectangle, starting from the directions away from | 
|  | // the source rectangle and copy the data. This way we ensure we don't | 
|  | // overwrite anything we still need to copy. | 
|  | val (dx0, dx1) = (math.max(0, math.min(width - 1, col + tx + w - 1)), math.max(0, math.min(width, col + tx))) match { | 
|  | case dx if tx > 0 => dx | 
|  | case dx => dx.swap | 
|  | } | 
|  | val (dy0, dy1) = (math.max(0, math.min(height - 1, row + ty + h - 1)), math.max(0, math.min(height, row + ty))) match { | 
|  | case dy if ty > 0 => dy | 
|  | case dy => dy.swap | 
|  | } | 
|  | val (sx, sy) = (if (tx > 0) -1 else 1, if (ty > 0) -1 else 1) | 
|  | // Copy values to destination rectangle if there source is valid. | 
|  | var changed = false | 
|  | for (ny <- dy0 to dy1 by sy) { | 
|  | val nl = buffer(ny) | 
|  | val nc = color(ny) | 
|  | ny - ty match { | 
|  | case oy if oy >= 0 && oy < height => | 
|  | val ol = buffer(oy) | 
|  | val oc = color(oy) | 
|  | for (nx <- dx0 to dx1 by sx) nx - tx match { | 
|  | case ox if ox >= 0 && ox < width => | 
|  | changed = changed || (nl(nx) != ol(ox)) || (nc(nx) != oc(ox)) | 
|  | nl(nx) = ol(ox) | 
|  | nc(nx) = oc(ox) | 
|  | case _ => /* Got no source column. */ | 
|  | } | 
|  | case _ => /* Got no source row. */ | 
|  | } | 
|  | } | 
|  | changed | 
|  | } | 
|  |  | 
|  | def load(nbt: NBTTagCompound): Unit = { | 
|  | val w = nbt.getInteger("width") max 1 min Settings.screenResolutionsByTier(2)._1 | 
|  | val h = nbt.getInteger("height") max 1 min Settings.screenResolutionsByTier(2)._2 | 
|  | size = (w, h) | 
|  |  | 
|  | val b = nbt.getTagList("buffer") | 
|  | for (i <- 0 until math.min(h, b.tagCount)) { | 
|  | b.tagAt(i) match { | 
|  | case tag: NBTTagString => set(0, i, tag.data, vertical = false) | 
|  | case _ => | 
|  | } | 
|  | } | 
|  |  | 
|  | val depth = ColorDepth.values.apply(nbt.getInteger("depth") min (ColorDepth.values.length - 1) max 0) | 
|  | _format = PackedColor.Depth.format(depth) | 
|  | _format.load(nbt) | 
|  | foreground = PackedColor.Color(nbt.getInteger("foreground"), nbt.getBoolean("foregroundIsPalette")) | 
|  | background = PackedColor.Color(nbt.getInteger("background"), nbt.getBoolean("backgroundIsPalette")) | 
|  |  | 
|  | val c = nbt.getTagList("color") | 
|  | for (i <- 0 until h) { | 
|  | val rowColor = color(i) | 
|  | for (j <- 0 until w) { | 
|  | val index = j + i * w | 
|  | if (index < c.tagCount) { | 
|  | c.tagAt(index) match { | 
|  | case tag: NBTTagShort => rowColor(j) = tag.data | 
|  | case _ => | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | def save(nbt: NBTTagCompound): Unit = { | 
|  | nbt.setInteger("width", width) | 
|  | nbt.setInteger("height", height) | 
|  |  | 
|  | val b = new NBTTagList() | 
|  | for (i <- 0 until height) { | 
|  | b.appendTag(new NBTTagString(null, String.valueOf(buffer(i)))) | 
|  | } | 
|  | nbt.setTag("buffer", b) | 
|  |  | 
|  | nbt.setInteger("depth", _format.depth.ordinal) | 
|  | _format.save(nbt) | 
|  | nbt.setInteger("foreground", _foreground.value) | 
|  | nbt.setBoolean("foregroundIsPalette", _foreground.isPalette) | 
|  | nbt.setInteger("background", _background.value) | 
|  | nbt.setBoolean("backgroundIsPalette", _background.isPalette) | 
|  |  | 
|  | val c = new NBTTagList() | 
|  | for (i <- 0 until height) { | 
|  | val rowColor = color(i) | 
|  | for (j <- 0 until width) { | 
|  | c.appendTag(new NBTTagShort(null, rowColor(j))) | 
|  | } | 
|  | } | 
|  | nbt.setTag("color", c) | 
|  | } | 
|  |  | 
|  | override def toString = { | 
|  | val b = StringBuilder.newBuilder | 
|  | if (buffer.length > 0) { | 
|  | b.appendAll(buffer(0)) | 
|  | for (y <- 1 until height) { | 
|  | b.append('\n').appendAll(buffer(y)) | 
|  | } | 
|  | } | 
|  | b.toString() | 
|  | } | 
|  | } |