Lots of fun stuff, including virtual dimensions to help reduce warping of grid when printed to a terminal

This commit is contained in:
Mike 2020-08-02 00:20:36 -07:00
parent 3e3b3543b2
commit 978c6d9d8c

View File

@ -11,25 +11,66 @@ import time
class TextCanvas: class TextCanvas:
__DEFAULT_TERMINAL_WIDTH = 80 __DEFAULT_WIDTH = 80
__DEFAULT_TERMINAL_HEIGHT = 22 __DEFAULT_HEIGHT = 22
__TERMINAL_UPDATE_INTERVAL = 1 __TERMINAL_UPDATE_INTERVAL = 1
__DRAW_RESOLUTION = 3 __BACKGROUND_CHARACTER = " "
def __init__(self): __DRAW_RESOLUTION = 3
__DEFAULT_TEXT_HEIGHT_TO_WIDTH_RATIO = 2.0
def __init__(
self,
output_width=None, output_height=None, text_to_height_width=None,
auto_adjust_to_terminal=True,
print_upward=True
):
self.__terminal_width = self.__DEFAULT_TERMINAL_WIDTH self.__lock = threading.RLock()
self.__terminal_height = self.__DEFAULT_TERMINAL_HEIGHT
# Real dimensions
if output_width is None:
output_width = self.__DEFAULT_WIDTH
if output_height is None:
output_height = self.__DEFAULT_HEIGHT
self.__grid_width = output_width
self.__grid_height = output_height
# Virtual dimensions
if text_to_height_width is None:
text_to_height_width = self.__DEFAULT_TEXT_HEIGHT_TO_WIDTH_RATIO
self.__text_to_height_width = text_to_height_width
self.__virtual_width = None
self.__virtual_height = None
self._auto_calculate_virtual_dimensions(acquire_locks=False)
self._update_virtual_ratios(acquire_locks=False)
# Terminal stuff
self.__terminal_width = 0
self.__terminal_height = 0
self.__auto_adjust_to_terminal = auto_adjust_to_terminal
self.__last_terminal_dimensions_update = time.time() self.__last_terminal_dimensions_update = time.time()
self._update_terminal_dimensions(force=True) if self.__auto_adjust_to_terminal:
self._update_terminal_dimensions(force=True)
#
self.__print_upward = print_upward
# Actual grid
self.__grid = [] self.__grid = []
self.__render_string = "" self.__render_string = ""
self.__is_dirty = True self.__is_dirty = True
self.__last_render_time = time.time() self.__last_render_time = time.time()
def _update_virtual_ratios(self, acquire_locks=True):
self.__lock = threading.RLock() with RAIILock(self.__lock, defer=not acquire_locks):
self.__virtual_height_per_grid_height = self.__virtual_height / self.__grid_height
self.__virtual_width_per_grid_width = self.__virtual_width / self.__grid_width
print("Virtual ratios:", self.__virtual_width_per_grid_width, self.__virtual_height_per_grid_height)
def _update_render_if_dirty(self, acquire_locks=True): def _update_render_if_dirty(self, acquire_locks=True):
@ -42,11 +83,9 @@ class TextCanvas:
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
self.clear(acquire_locks=acquire_locks) self._update_render_string(acquire_locks=False)
self._update_render_string(acquire_locks=acquire_locks)
self.__last_render_time = time.time() self.__last_render_time = time.time()
self.__is_dirty = False self.__is_dirty = False
def _update_render_string(self, acquire_locks=True): def _update_render_string(self, acquire_locks=True):
@ -62,21 +101,31 @@ class TextCanvas:
# Don't need that last line feed # Don't need that last line feed
s = s[:-1] s = s[:-1]
self.__grid_string = s self.__render_string = s
def get(self, acquire_locks=True): def get(self, acquire_locks=True):
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
self._update_render_if_dirty(acquire_locks=acquire_locks) self._update_render_if_dirty(acquire_locks=False)
return self.__grid_string return self.__render_string
def print(self, acquire_locks=True): def print(self, acquire_locks=True):
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
s = self.get(acquire_locks=acquire_locks) self._update_render_if_dirty(acquire_locks=False)
s = ""
# Move the cursor up to compensate
if self.__print_upward:
up_count = self.__grid_height
s += ("\033[F" * up_count)
# Grab render
s += self.get(acquire_locks=False)
print(s) print(s)
@ -95,7 +144,7 @@ class TextCanvas:
x = int(x) x = int(x)
for c in text: for c in text:
self.write(x=x, y=y, character=c, acquire_locks=acquire_locks) self.write(x=x, y=y, character=c, acquire_locks=False)
x += 1 x += 1
def draw_line(self, start_x, start_y, end_x, end_y, character="*", acquire_locks=True): def draw_line(self, start_x, start_y, end_x, end_y, character="*", acquire_locks=True):
@ -129,7 +178,7 @@ class TextCanvas:
pos_x_rounded = round(pos_x) pos_x_rounded = round(pos_x)
pos_y_rounded = round(pos_y) pos_y_rounded = round(pos_y)
self.write(x=pos_x_rounded, y=pos_y_rounded, character=character, acquire_locks=acquire_locks) self.write(x=pos_x_rounded, y=pos_y_rounded, character=character, acquire_locks=False)
pos_x += increment_x pos_x += increment_x
pos_y += increment_y pos_y += increment_y
@ -138,8 +187,7 @@ class TextCanvas:
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
x = round(x) x, y = self._translate_virtual_to_internal_coordinates(x=x, y=y)
y = round(y)
if 0 <= y < len(self.__grid): if 0 <= y < len(self.__grid):
if 0 <= x < len(self.__grid[y]): if 0 <= x < len(self.__grid[y]):
@ -152,17 +200,17 @@ class TextCanvas:
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
self._update_terminal_dimensions(acquire_locks=acquire_locks) self._auto_adjust_to_terminal(acquire_locks=False)
self.__grid = [] self.__grid = []
for y in range(self.__terminal_height): for y in range(self.__grid_height):
self.__grid.append([]) self.__grid.append([])
for x in range(self.__terminal_width): for x in range(self.__grid_width):
self.__grid[y].append(" ") self.__grid[y].append(self.__BACKGROUND_CHARACTER)
self.__is_dirty = True self.__is_dirty = True
@ -170,13 +218,63 @@ class TextCanvas:
with RAIILock(self.__lock, defer=not acquire_locks): with RAIILock(self.__lock, defer=not acquire_locks):
height = len(self.__grid) return self.get_virtual_dimensions()
if height == 0:
width = 0 def get_virtual_dimensions(self, acquire_locks=True):
else:
width = len(self.__grid[0]) with RAIILock(self.__lock, defer=not acquire_locks):
return width, height return self.__virtual_width, self.__virtual_height
def _get_internal_dimensions(self, acquire_locks=True):
with RAIILock(self.__lock, defer=not acquire_locks):
return self.__grid_width, self.__grid_height
def _translate_virtual_to_internal_coordinates(self, x, y):
x_translated = x / self.__virtual_width_per_grid_width
y_translated = y / self.__virtual_height_per_grid_height
x_translated = round(x_translated)
y_translated = round(y_translated)
return x_translated, y_translated
def _is_time_to_update_terminal_dimensions(self):
if self.__auto_adjust_to_terminal:
now = time.time()
if now - self.__last_terminal_dimensions_update > self.__TERMINAL_UPDATE_INTERVAL:
return True
return False
def _auto_adjust_to_terminal(self, force=False, acquire_locks=True):
if not force and not self.__auto_adjust_to_terminal:
return
with RAIILock(self.__lock, defer=not acquire_locks):
self._update_terminal_dimensions(acquire_locks=False)
self.__grid_width = self.__terminal_width
self.__grid_height = self.__terminal_height
self._auto_calculate_virtual_dimensions(acquire_locks=False)
def _auto_calculate_virtual_dimensions(self, acquire_locks=True):
with RAIILock(self.__lock, defer=not acquire_locks):
self.__virtual_width = self.__grid_width
self.__virtual_height = self.__grid_height * self.__text_to_height_width
self._update_virtual_ratios(acquire_locks=False)
def _update_terminal_dimensions(self, force=False, acquire_locks=True): def _update_terminal_dimensions(self, force=False, acquire_locks=True):
@ -184,7 +282,7 @@ class TextCanvas:
now = time.time() now = time.time()
if force is not False and now - self.__last_terminal_dimensions_update < self.__TERMINAL_UPDATE_INTERVAL: if force is not False and not self._is_time_to_update_terminal_dimensions():
return return
self.__last_terminal_dimensions_update = now self.__last_terminal_dimensions_update = now
@ -192,6 +290,8 @@ class TextCanvas:
self.__terminal_width = width self.__terminal_width = width
self.__terminal_height = height self.__terminal_height = height
self._auto_calculate_virtual_dimensions(acquire_locks=False)
@staticmethod @staticmethod
def determine_terminal_dimensions(): def determine_terminal_dimensions():
@ -199,4 +299,3 @@ class TextCanvas:
columns, lines = os.get_terminal_size() columns, lines = os.get_terminal_size()
return columns, lines return columns, lines