Lots of fun stuff, including virtual dimensions to help reduce warping of grid when printed to a terminal
This commit is contained in:
parent
3e3b3543b2
commit
978c6d9d8c
161
TextCanvas.py
161
TextCanvas.py
@ -11,25 +11,66 @@ import time
|
||||
|
||||
class TextCanvas:
|
||||
|
||||
__DEFAULT_TERMINAL_WIDTH = 80
|
||||
__DEFAULT_TERMINAL_HEIGHT = 22
|
||||
__DEFAULT_WIDTH = 80
|
||||
__DEFAULT_HEIGHT = 22
|
||||
__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.__terminal_height = self.__DEFAULT_TERMINAL_HEIGHT
|
||||
self.__lock = threading.RLock()
|
||||
|
||||
# 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._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.__render_string = ""
|
||||
self.__is_dirty = True
|
||||
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):
|
||||
|
||||
@ -42,11 +83,9 @@ class TextCanvas:
|
||||
|
||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||
|
||||
self.clear(acquire_locks=acquire_locks)
|
||||
self._update_render_string(acquire_locks=acquire_locks)
|
||||
self._update_render_string(acquire_locks=False)
|
||||
|
||||
self.__last_render_time = time.time()
|
||||
|
||||
self.__is_dirty = False
|
||||
|
||||
def _update_render_string(self, acquire_locks=True):
|
||||
@ -62,21 +101,31 @@ class TextCanvas:
|
||||
# Don't need that last line feed
|
||||
s = s[:-1]
|
||||
|
||||
self.__grid_string = s
|
||||
self.__render_string = s
|
||||
|
||||
def get(self, acquire_locks=True):
|
||||
|
||||
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):
|
||||
|
||||
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)
|
||||
|
||||
@ -95,7 +144,7 @@ class TextCanvas:
|
||||
x = int(x)
|
||||
|
||||
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
|
||||
|
||||
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_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_y += increment_y
|
||||
@ -138,8 +187,7 @@ class TextCanvas:
|
||||
|
||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||
|
||||
x = round(x)
|
||||
y = round(y)
|
||||
x, y = self._translate_virtual_to_internal_coordinates(x=x, y=y)
|
||||
|
||||
if 0 <= y < len(self.__grid):
|
||||
if 0 <= x < len(self.__grid[y]):
|
||||
@ -152,17 +200,17 @@ class TextCanvas:
|
||||
|
||||
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 = []
|
||||
|
||||
for y in range(self.__terminal_height):
|
||||
for y in range(self.__grid_height):
|
||||
|
||||
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
|
||||
|
||||
@ -170,13 +218,63 @@ class TextCanvas:
|
||||
|
||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||
|
||||
height = len(self.__grid)
|
||||
if height == 0:
|
||||
width = 0
|
||||
else:
|
||||
width = len(self.__grid[0])
|
||||
return self.get_virtual_dimensions()
|
||||
|
||||
def get_virtual_dimensions(self, acquire_locks=True):
|
||||
|
||||
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):
|
||||
|
||||
@ -184,7 +282,7 @@ class TextCanvas:
|
||||
|
||||
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
|
||||
self.__last_terminal_dimensions_update = now
|
||||
|
||||
@ -192,6 +290,8 @@ class TextCanvas:
|
||||
|
||||
self.__terminal_width = width
|
||||
self.__terminal_height = height
|
||||
|
||||
self._auto_calculate_virtual_dimensions(acquire_locks=False)
|
||||
|
||||
@staticmethod
|
||||
def determine_terminal_dimensions():
|
||||
@ -199,4 +299,3 @@ class TextCanvas:
|
||||
columns, lines = os.get_terminal_size()
|
||||
|
||||
return columns, lines
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user