2020-02-12 12:01:12 +01:00
|
|
|
import os
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
from time import sleep # debug
|
|
|
|
|
|
|
|
class Color:
|
|
|
|
cache_closest_printable_color = 0
|
|
|
|
|
|
|
|
def __init__(self, r: float = 0.0, g: float = 0.0, b: float = 0.0):
|
|
|
|
self.set(r, g, b)
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
def set(self, r, g, b):
|
|
|
|
self.r, self.g, self.b = r, g, b
|
|
|
|
|
|
|
|
def update(self):
|
|
|
|
self._r, self._g, self._b = self.r, self.g, self.b
|
|
|
|
|
|
|
|
def hasChange(self) -> bool:
|
|
|
|
return any(x != y for x, y in {self._r: self.r, self._g: self.g, self._b: self.b}.items())
|
|
|
|
|
|
|
|
def distance(self, to) -> float:
|
|
|
|
dr = self.r - to.r
|
|
|
|
dg = self.g - to.g
|
|
|
|
db = self.b - to.b
|
|
|
|
return (dr * dr + dg * dg + db * db) ** .5
|
|
|
|
|
|
|
|
def getClosestPrintableColor(self) -> int:
|
|
|
|
if not self.hasChange():
|
|
|
|
return self.cache_closest_printable_color
|
|
|
|
|
|
|
|
closest_distance = float("inf")
|
|
|
|
closest_value = 0
|
|
|
|
for k, v in COLORS.items():
|
|
|
|
distance = self.distance(k)
|
|
|
|
if closest_distance > distance:
|
|
|
|
closest_distance = distance
|
|
|
|
closest_value = v
|
|
|
|
|
|
|
|
self.cache_closest_printable_color = closest_value
|
|
|
|
self.update()
|
|
|
|
|
|
|
|
return closest_value
|
|
|
|
|
|
|
|
class Pixel:
|
|
|
|
char = " "
|
|
|
|
text_color = Color()
|
|
|
|
background_color = Color()
|
|
|
|
|
|
|
|
cached_text_color_value = -1
|
|
|
|
cached_background_color_value = -1
|
|
|
|
|
|
|
|
def __str__(self) -> str:
|
|
|
|
color = ""
|
|
|
|
|
|
|
|
text_color = self.text_color.getClosestPrintableColor()
|
|
|
|
if text_color != self.cached_text_color_value:
|
|
|
|
color += COLOR_FORMAT % (text_color + COLOR_TEXT)
|
|
|
|
self.cached_text_color_value = text_color
|
|
|
|
|
|
|
|
background_color = self.background_color.getClosestPrintableColor()
|
|
|
|
if background_color != self.cached_background_color_value:
|
|
|
|
color += COLOR_FORMAT % (background_color + COLOR_BACKGROUND)
|
|
|
|
self.cached_background_color_value = background_color
|
|
|
|
|
|
|
|
|
|
|
|
return color + self.char
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
self.cached_text_color_value = -1
|
|
|
|
self.cached_background_color_value = -1
|
|
|
|
|
|
|
|
class Screen:
|
|
|
|
def __init__(self, width: int, height: int, std_handle: int = 0, render_funcs = []):
|
|
|
|
self.width = width
|
|
|
|
self.height = height
|
|
|
|
self.std_handle = std_handle
|
|
|
|
self.render_funcs = render_funcs
|
|
|
|
|
|
|
|
self.time_start = datetime.datetime.now()
|
|
|
|
self.buffer = ""
|
2020-02-12 13:11:57 +01:00
|
|
|
|
|
|
|
self.dynamic = self.height < 0 and self.width < 0
|
2020-02-12 12:01:12 +01:00
|
|
|
|
|
|
|
def get_time(self):
|
|
|
|
return (datetime.datetime.now() - self.time_start).total_seconds()
|
|
|
|
|
|
|
|
def write(self, string):
|
|
|
|
self.buffer += string
|
|
|
|
|
|
|
|
def flush(self):
|
|
|
|
print(self.buffer, end="", flush=True)
|
|
|
|
self.buffer = ""
|
|
|
|
|
|
|
|
def mainloop(self):
|
|
|
|
pixel = Pixel()
|
|
|
|
|
|
|
|
while True:
|
|
|
|
width, height = os.get_terminal_size(self.std_handle)
|
2020-02-12 13:11:57 +01:00
|
|
|
|
|
|
|
if self.dynamic:
|
|
|
|
self.width, self.height = width, height
|
|
|
|
|
2020-02-12 12:01:12 +01:00
|
|
|
if width < self.width or height < self.height:
|
|
|
|
Screen.clear()
|
|
|
|
print("Unable to print the screen, a terminal with %dx%d (and above) chars are required to continue" % (self.width, self.height))
|
|
|
|
while width < self.width or height < self.height:
|
|
|
|
width, height = os.get_terminal_size(self.std_handle)
|
|
|
|
|
|
|
|
time = self.get_time()
|
|
|
|
|
2020-02-12 13:11:57 +01:00
|
|
|
resolution = (self.width, self.height)
|
2020-02-12 12:01:12 +01:00
|
|
|
for y in range(self.height):
|
|
|
|
for x in range(self.width):
|
|
|
|
for render_func in self.render_funcs:
|
2020-02-12 13:11:57 +01:00
|
|
|
render_func(pixel, resolution, (x, y), time)
|
2020-02-12 12:01:12 +01:00
|
|
|
self.write(str(pixel))
|
|
|
|
pixel.reset()
|
|
|
|
self.write("\x1b[0m\n") # newline
|
|
|
|
|
|
|
|
#Screen.clear()
|
|
|
|
#self.flush()
|
|
|
|
self.new_flush()
|
|
|
|
#sleep(.01)
|
|
|
|
|
|
|
|
def new_flush(self):
|
|
|
|
self.buffer = "\x1b[0;0H" + self.buffer
|
|
|
|
self.flush()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def clear():
|
|
|
|
print("\x1b[2J", end="", flush=True)
|
|
|
|
|
|
|
|
COLOR_RESET = 0
|
|
|
|
COLOR_TEXT = 30
|
|
|
|
COLOR_BACKGROUND = 40
|
|
|
|
COLOR_FORMAT = "\x1b[%dm"
|
|
|
|
COLORS = {
|
|
|
|
Color(0.0, 0.0, 0.0): 0,
|
|
|
|
Color(1.0, 0.0, 0.0): 1,
|
|
|
|
Color(0.0, 1.0, 0.0): 2,
|
|
|
|
Color(1.0, 1.0, 0.0): 3,
|
|
|
|
Color(0.0, 0.0, 1.0): 4,
|
|
|
|
Color(1.0, 0.0, 1.0): 5,
|
|
|
|
Color(0.0, 1.0, 1.0): 6,
|
|
|
|
Color(1.0, 1.0, 1.0): 7
|
|
|
|
}
|