142 lines
3.5 KiB
Python
142 lines
3.5 KiB
Python
|
|
import sys
|
|
import shutil
|
|
|
|
from .constants import INT_INFINITY
|
|
from .drawtarget import DrawTarget
|
|
from .textstyle import TextStyle
|
|
from .strwidth import charwidth
|
|
|
|
|
|
class Attr:
|
|
|
|
RESET = "0"
|
|
BOLD = "1"
|
|
UNDERSCORE = "4"
|
|
BLINK = "5"
|
|
REVERSE = "7"
|
|
CONCEALED = "8"
|
|
|
|
|
|
FG_DEFAULT = "39"
|
|
BG_DEFAULT = "49"
|
|
|
|
FG_COLORS = [str(i) for i in list(range(30, 38)) + list(range(90, 98))]
|
|
BG_COLORS = [str(i) for i in list(range(40, 48)) + list(range(100, 108))]
|
|
|
|
ATTRS = {
|
|
TextStyle.BOLD: BOLD,
|
|
TextStyle.REVERSE: REVERSE,
|
|
TextStyle.UNDERSCORE: UNDERSCORE,
|
|
TextStyle.BLINK: BLINK
|
|
}
|
|
|
|
class Screen(DrawTarget):
|
|
|
|
|
|
def __init__(self, out=sys.stdout, always_reset=False, blink_bright_background=False):
|
|
self.out = out
|
|
self.width = 0
|
|
self.height = 0
|
|
self.blink_bright_background = blink_bright_background # use the blink attribute for bright backgrounds
|
|
self.always_reset = always_reset or blink_bright_background # always reset if the style is different than the previous one
|
|
self.update_size()
|
|
|
|
def update_size(self):
|
|
size = shutil.get_terminal_size()
|
|
self.width = size.columns
|
|
self.height = size.lines
|
|
|
|
def move(self, x, y):
|
|
self.out.write("\033[{};{}f".format(y+1, x+1))
|
|
|
|
def addstr(self, text):
|
|
self.out.write(text)
|
|
|
|
def style(self, style, previous=None):
|
|
if style is None:
|
|
style = TextStyle.default
|
|
if style == previous:
|
|
return
|
|
parts = []
|
|
reset = False
|
|
if style.fg is None or style.bg is None or previous is None or previous != style and self.always_reset:
|
|
parts.append(Attr.RESET)
|
|
reset = True
|
|
else :
|
|
for attr, enabled in style.attr.items():
|
|
if not enabled and previous.attr[attr]:
|
|
parts.append(Attr.RESET)
|
|
reset = True
|
|
if style.fg is not None and (reset or style.fg != previous.fg):
|
|
parts.append(Attr.FG_COLORS[style.fg])
|
|
if style.bg is not None and (reset or style.bg != previous.bg):
|
|
parts.append(Attr.BG_COLORS[style.bg])
|
|
if style.bg > 7 and self.blink_bright_background:
|
|
parts.append(Attr.BLINK)
|
|
for attr, enabled in style.attr.items():
|
|
if enabled and (reset or not previous.attr[attr]):
|
|
parts.append(Attr.ATTRS[attr])
|
|
ansistyle = "\033[" + ";".join(parts) + "m"
|
|
self.out.write(ansistyle)
|
|
|
|
def write(self, x, y, text, style=None):
|
|
self.move(x, y)
|
|
self.style(style)
|
|
self.addstr(text)
|
|
|
|
def clear(self):
|
|
self.out.write("\033[0m\033[2J")
|
|
|
|
def clear_line(self):
|
|
self.out.write("\033[K")
|
|
|
|
def skip(self, amount=1):
|
|
if amount == 0:
|
|
return
|
|
if amount == 1:
|
|
stramount = ""
|
|
else:
|
|
stramount = str(abs(amount))
|
|
self.out.write("\033[{}{}".format(stramount, ("C" if amount >= 0 else "D")))
|
|
|
|
def draw_pad(self, pad, scr_x=0, scr_y=0, width=INT_INFINITY, height=INT_INFINITY, pad_x=0, pad_y=0):
|
|
screen = self
|
|
width = min(width, screen.width - scr_x, pad.width - pad_x)
|
|
height = min(height, screen.height - scr_y, pad.height - pad_y)
|
|
last_style = None
|
|
for y in range(height):
|
|
screen.move(scr_x, scr_y+y)
|
|
skip = 0
|
|
line_y = pad_y + y
|
|
for cell in pad.get_line(pad_x, line_y, width):
|
|
if cell is None:
|
|
skip += 1
|
|
continue
|
|
if skip != 0:
|
|
screen.skip(skip)
|
|
skip = 0
|
|
style, char = cell
|
|
screen.style(style, last_style)
|
|
last_style = style
|
|
screen.addstr(char)
|
|
skip += 1 - charwidth(char)
|
|
|
|
def hide_cursor(self):
|
|
self.out.write("\033[?25l")
|
|
|
|
def show_cursor(self):
|
|
self.out.write("\033[?25h")
|
|
|
|
def finalize(self):
|
|
self.style(None)
|
|
self.move(0, self.height - 1)
|
|
self.show_cursor()
|
|
self.out.flush()
|
|
|
|
def update(self):
|
|
self.out.flush()
|
|
|
|
|
|
Screen.default = Screen()
|