134 lines
3.6 KiB
Nim
134 lines
3.6 KiB
Nim
import unicode
|
|
import escapeSequences
|
|
|
|
type
|
|
Buffer* = ref object
|
|
# AS IS ON SCREEN VALUES
|
|
stdout: File # usually you want to put stdout here
|
|
displayed: seq[Cell]
|
|
displayX, displayY: int # where the cursor is on display
|
|
positionX, positionY: int # where the buffer's top left corner is
|
|
|
|
# SIMULATED VALUES
|
|
buffered: seq[Cell]
|
|
# cursor position
|
|
bufferX, bufferY: int # where the cursor is, in buffer
|
|
width, height: int
|
|
# current style we're writing with
|
|
cursorAttributes: seq[uint8]
|
|
|
|
ForegroundColors* = enum
|
|
fcBlack = 30, fcRed, fcGreen, fcYellow, fcBlue,
|
|
fcMagenta, fcCyan, fcLightGray,
|
|
fcDefault = 39,
|
|
fcDarkGray = 90, fcLightRed, fcLightGreen,
|
|
fcLightYellow, fcLightBlue, fcLightMagenta,
|
|
fcLightCyan, fcWhite
|
|
BackgroundColors* = enum
|
|
bcBlack, bcRed, bcGreen, bcYellow, bcBlue,
|
|
bcMagenta, bcCyan, bcLightGray,
|
|
bcDefault = 49,
|
|
bcDarkGray = 100, bcLightRed, bcLightGreen,
|
|
bcLightYellow, bcLightBlue, bcLightMagenta,
|
|
bcLightCyan, bcWhite
|
|
FontStyle* = enum
|
|
# these two are the most widely supported
|
|
fsReset = 0,
|
|
fsBold = 1,
|
|
fsUnderlined = 4,
|
|
Cell = object
|
|
attributes: seq[uint8]
|
|
text: Rune
|
|
|
|
func getCursorPos*(buf: Buffer): (int, int) =
|
|
(buf.bufferX, buf.bufferY)
|
|
|
|
proc setCursorPos*(buf: Buffer, x, y: int) =
|
|
if x < 0 or y < 0 or x >= buf.width or y >= buf.height:
|
|
raise newException(ValueError, "Provided x or y out of bounds for SetCursorPos.")
|
|
buf.bufferX = x
|
|
buf.bufferY = y
|
|
|
|
func getCursorXPos*(buf: Buffer): int =
|
|
buf.bufferX
|
|
|
|
proc setCursorXPos*(buf: Buffer, x: int) =
|
|
if x < 0 or x >= buf.width:
|
|
raise newException(ValueError, "Provided x is out of bounds.")
|
|
buf.bufferX = x
|
|
|
|
|
|
proc setAttributes*(buf: Buffer, attributes: seq[uint8]) =
|
|
buf.cursorAttributes = attributes
|
|
|
|
proc addAttribute*(buf: Buffer, attribute: uint8) =
|
|
buf.cursorAttributes.add(attribute)
|
|
|
|
proc clearAttributes*(buf: Buffer) =
|
|
buf.cursorAttributes = @[]
|
|
|
|
proc clear*(buf: Buffer) =
|
|
for i in 0..buf.buffered.high():
|
|
buf.buffered[i] = Cell(text: Rune(0))
|
|
buf.bufferX = 0
|
|
buf.bufferY = 0
|
|
|
|
template lineStart(buf: Buffer, line: int): int =
|
|
# returns where line <line> starts
|
|
buf.width * line
|
|
|
|
template lineEnd(buf: Buffer, line: int): int =
|
|
buf.lineStart(line + 1) - 1
|
|
|
|
proc clearLine*(buf: Buffer) =
|
|
for i in buf.lineStart(buf.bufferY)..buf.lineEnd(buf.bufferY):
|
|
buf.buffered[i] = Cell(text: Rune(0))
|
|
buf.bufferX = 0
|
|
|
|
proc write*(buf: Buffer, text: string) =
|
|
# convert text to characters
|
|
let runes = text.toRunes()
|
|
|
|
# if it would go out of the screen, crash
|
|
if buf.bufferX + runes.len() >= buf.width:
|
|
raise newException(ValueError, "Text too long, would overflow the buffer.")
|
|
|
|
let fromPos = buf.lineStart(buf.bufferY) + buf.bufferX
|
|
let toPos = fromPos + runes.high()
|
|
|
|
for i in fromPos..toPos:
|
|
buf.buffered[i] = Cell(text: runes[i-fromPos], attributes: buf.cursorAttributes)
|
|
|
|
|
|
proc redraw*(buf: Buffer) =
|
|
var toPrint = ""
|
|
# save cursor state
|
|
let oldDisplayX = buf.displayX
|
|
let oldDisplayY = buf.displayY
|
|
|
|
# go over display and buffer to find any differences
|
|
for i in 0..buf.buffered.high():
|
|
let cellBuffer = buf.buffered[i]
|
|
let cellDisplay = buf.displayed[i]
|
|
# TODO continue here :)
|
|
|
|
|
|
|
|
|
|
# finish
|
|
toPrint &= escCursorPos(oldDisplayX, oldDisplayY)
|
|
buf.stdout.write(toPrint)
|
|
|
|
|
|
func getSize*(buf: Buffer): (int, int) =
|
|
(buf.width, buf.height)
|
|
|
|
proc resize*(buf: Buffer, newX, newY: int) =
|
|
raise newException(Defect, "Not implemented")
|
|
|
|
func getPosition*(buf: Buffer): (int, int) =
|
|
(buf.positionX, buf.positionY)
|
|
|
|
proc setPosition*(buf: Buffer, x, y: int) =
|
|
buf.positionX = x
|
|
buf.positionY = y |